From f89a682d982d0f28a57fdb6e1993ece883452166 Mon Sep 17 00:00:00 2001 From: Shrenuj Bansal Date: Thu, 29 Sep 2022 10:56:57 +0000 Subject: [PATCH] Add Mpool ref to raft state and rearrange some APIs --- api/api_full.go | 5 +- api/docgen/docgen.go | 4 +- api/mocks/mock_full.go | 5 +- api/proxy_gen.go | 7 +- api/types.go | 58 ++++ build/openrpc/full.json.gz | Bin 28646 -> 28639 bytes build/openrpc/gateway.json.gz | Bin 5078 -> 5078 bytes build/openrpc/miner.json.gz | Bin 16030 -> 16030 bytes build/openrpc/worker.json.gz | Bin 5260 -> 5259 bytes chain/messagepool/messagepool.go | 5 + chain/messagepool/provider.go | 6 +- chain/messagesigner/messagesigner.go | 10 +- .../messagesigner/messagesigner_consensus.go | 3 +- cli/util/api.go | 4 +- documentation/en/default-lotus-config.toml | 302 ++++++++++++++++++ itests/raft_messagesigner_test.go | 114 +++---- lib/consensus/raft/config.go | 125 +++++--- lib/consensus/raft/consensus.go | 86 ++--- lib/consensus/raft/raft.go | 6 +- node/builder_chain.go | 4 +- node/config/def.go | 38 ++- node/config/doc_gen.go | 156 +++++---- node/config/types.go | 44 ++- node/impl/full.go | 5 +- node/impl/full/raft.go | 11 +- node/modules/mpoolnonceapi.go | 4 +- 26 files changed, 705 insertions(+), 297 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 91f290170..ab1d9d03e 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -29,7 +29,6 @@ import ( lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/power" "github.com/filecoin-project/lotus/chain/types" - consensus2 "github.com/filecoin-project/lotus/lib/consensus/raft" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" ) @@ -753,8 +752,8 @@ type FullNode interface { // the path specified when calling CreateBackup is within the base path CreateBackup(ctx context.Context, fpath string) error //perm:admin - RaftState(ctx context.Context) (*consensus2.RaftState, error) //perm:read - RaftLeader(ctx context.Context) (peer.ID, error) //perm:read + RaftState(ctx context.Context) (*RaftStateData, error) //perm:read + RaftLeader(ctx context.Context) (peer.ID, error) //perm:read } type StorageAsk struct { diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index d3ac772b6..26ada4f4c 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -40,7 +40,6 @@ import ( "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - consensus "github.com/filecoin-project/lotus/lib/consensus/raft" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" sealing "github.com/filecoin-project/lotus/storage/pipeline" @@ -341,7 +340,7 @@ func init() { addExample(map[string]bitfield.BitField{ "": bitfield.NewFromSet([]uint64{5, 6, 7, 10}), }) - addExample(&consensus.RaftState{ + addExample(&api.RaftStateData{ NonceMap: make(map[address.Address]uint64), MsgUuids: make(map[uuid.UUID]*types.SignedMessage), }) @@ -357,6 +356,7 @@ func init() { Headers: nil, }, }) + } func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 583cbd67f..fa5cafd0e 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -37,7 +37,6 @@ import ( miner0 "github.com/filecoin-project/lotus/chain/actors/builtin/miner" types "github.com/filecoin-project/lotus/chain/types" alerting "github.com/filecoin-project/lotus/journal/alerting" - consensus "github.com/filecoin-project/lotus/lib/consensus/raft" dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" imports "github.com/filecoin-project/lotus/node/repo/imports" ) @@ -2260,10 +2259,10 @@ func (mr *MockFullNodeMockRecorder) RaftLeader(arg0 interface{}) *gomock.Call { } // RaftState mocks base method. -func (m *MockFullNode) RaftState(arg0 context.Context) (*consensus.RaftState, error) { +func (m *MockFullNode) RaftState(arg0 context.Context) (*api.RaftStateData, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RaftState", arg0) - ret0, _ := ret[0].(*consensus.RaftState) + ret0, _ := ret[0].(*api.RaftStateData) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/api/proxy_gen.go b/api/proxy_gen.go index c3e653685..228e21bbb 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -36,7 +36,6 @@ import ( lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/journal/alerting" - consensus2 "github.com/filecoin-project/lotus/lib/consensus/raft" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/lotus/node/repo/imports" "github.com/filecoin-project/lotus/storage/pipeline/sealiface" @@ -342,7 +341,7 @@ type FullNodeStruct struct { RaftLeader func(p0 context.Context) (peer.ID, error) `perm:"read"` - RaftState func(p0 context.Context) (*consensus2.RaftState, error) `perm:"read"` + RaftState func(p0 context.Context) (*RaftStateData, error) `perm:"read"` StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` @@ -2465,14 +2464,14 @@ func (s *FullNodeStub) RaftLeader(p0 context.Context) (peer.ID, error) { return *new(peer.ID), ErrNotSupported } -func (s *FullNodeStruct) RaftState(p0 context.Context) (*consensus2.RaftState, error) { +func (s *FullNodeStruct) RaftState(p0 context.Context) (*RaftStateData, error) { if s.Internal.RaftState == nil { return nil, ErrNotSupported } return s.Internal.RaftState(p0) } -func (s *FullNodeStub) RaftState(p0 context.Context) (*consensus2.RaftState, error) { +func (s *FullNodeStub) RaftState(p0 context.Context) (*RaftStateData, error) { return nil, ErrNotSupported } diff --git a/api/types.go b/api/types.go index 5a1ceadd5..e9e2494ca 100644 --- a/api/types.go +++ b/api/types.go @@ -337,3 +337,61 @@ type ForkUpgradeParams struct { UpgradeChocolateHeight abi.ChainEpoch UpgradeOhSnapHeight abi.ChainEpoch } + +type NonceMapType map[address.Address]uint64 +type MsgUuidMapType map[uuid.UUID]*types.SignedMessage + +type RaftStateData struct { + NonceMap NonceMapType + MsgUuids MsgUuidMapType +} + +func (n *NonceMapType) MarshalJSON() ([]byte, error) { + marshalled := make(map[string]uint64) + for a, n := range *n { + marshalled[a.String()] = n + } + return json.Marshal(marshalled) +} + +func (n *NonceMapType) UnmarshalJSON(b []byte) error { + unmarshalled := make(map[string]uint64) + err := json.Unmarshal(b, &unmarshalled) + if err != nil { + return err + } + *n = make(map[address.Address]uint64) + for saddr, nonce := range unmarshalled { + a, err := address.NewFromString(saddr) + if err != nil { + return err + } + (*n)[a] = nonce + } + return nil +} + +func (m *MsgUuidMapType) MarshalJSON() ([]byte, error) { + marshalled := make(map[string]*types.SignedMessage) + for u, msg := range *m { + marshalled[u.String()] = msg + } + return json.Marshal(marshalled) +} + +func (m *MsgUuidMapType) UnmarshalJSON(b []byte) error { + unmarshalled := make(map[string]*types.SignedMessage) + err := json.Unmarshal(b, &unmarshalled) + if err != nil { + return err + } + *m = make(map[uuid.UUID]*types.SignedMessage) + for suid, msg := range unmarshalled { + u, err := uuid.Parse(suid) + if err != nil { + return err + } + (*m)[u] = msg + } + return nil +} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index aa285e2e72f3eb28342746f1d502c53c9f3ef1ad..5410699eaf6e49d099d72ccb721b1f2c5ccdc0e7 100644 GIT binary patch delta 20983 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 delta 20961 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

<;KgFs@In5;~f=Xy$jK& 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 KiB#{70tEn_Fx064 diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz index 3093644d667f8a16ca9308882121ff532a8ae5f7..e19da4dcb3ad20b04fbe4c8c064d63400706bd61 100644 GIT binary patch delta 3251 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 delta 3251 zcmV;k3{3OZC)OvhO$1T9=dY>QhdcuUF0Sl7hTU`gM&DQTG3oAn#Lf_yjH?(z+o+Qm z&+wsL!(0zUL`l{GNxhZvKe%!vo@F)kz<*i(dIxwiWqQB4(ztt46yMn|lb!@TX5W-6 z-2tLN*t&ZaNhqsT@Rx52+BXS2UkSe=a2rE?Sw$gd)==x^S}&LC} zkkxzDR@iuE5&yey-x^WO?Tb>ZBSgAd_^(=|Zq1>}A|`^O5{R2eO&+)I+B=YwKm}a_ z{pORO1)u`7%9B9`CIMTMXa-LK9h0~Q9|7Hy*akm;-Yb(CfXr$V1CY6iB!3^y=Io+y zR&`?kv(Sc}vZE{mk5$!%ylas*90jP~3BJHfPTqyo%^~H}w<2jTC$()lP{fBEB^Ohv zPl1(X|D|VTWq{?S0*QEv|y`uuS0mHyVpJ-^=<2T zrrXAU7PV=(Wu_q~v7Ei#@bPNFmCX^VI=^j|D7}Kr#}n)t_Dv3Du8<~CzkEv(IhdP4 z9~>Z8gX0t1@O+*ulP3|x5?N9NR!O=DGv5YlvS>}&h+A<(85>ijI#qk8wL1X<)TiP< z+Rb+Jb*uHd-Tv5o({H!?-S+EduitDYi&uSrYh-F-`P}Lt<|BTPNMqP}Rh+EAIZGl} z;W4x0n}Q6sGvuDS49^gQFa`1qDd!>Q0x44Y>9ea&TYDA$7y>be4j2v1=@iGzCC=UC zPG}>(VbM~fkhwqgZL%Hela=+!%KBvG?><>swl9DoC9q28u|U{U_ujpFWajaE;P+iASt&ZactlxO&rry!nnjM`jHFx%!1t&l* zTs%1gc6fuZg+>+}Aq)9ZM%DPq2UD2E%&BGPkpEkd#>}4_;mLXVUQ5B?CB~8nSxsTE-(LCPK2k(nWUxSEt`vab$mPWsQedk{zj%eU(5r>sgBCwXQEy%aglrQjjQTF| zK{`et1_C5q`$NZ}_Km-gn5wE*NQj}y-kXr|kXY(I2JZW>b|F!4gC|bY~%XGqlga`JXYKmOpm4N42aWzlJQs4bAEQ| z4}0B$Gl@fn7#tdg?U9h*l%##C`+8zN)2dk1r^YQib&&+BB5;2%!FXTR*W zfB7wyetQL1tpyBz0s#VUunF|owk*VeBJ0TZh^c&dWhKC}I!^;+nqUqv7%;>?`Dqqj z*(5iL;t!JszfxwF&87`?C8Ij+d#M=pK*dJt5XWXje;&hS z5L(bkDrXXtXrBYkga4_YWl?^$Ln31OoC5*rCB}<@3ym&g&WBVIFP( zX{9!ahTO<$$n+v=Tf4TkAN*`lKKrF!N>xP)FIU;j6=p#_tc^i`6H?#BaF3z}s2sgX z-#a6B`&WblwO-OW)K5*%>=%^G2&C+L?6M0?fL3#G;_mX-2YmUa8s3RVDa~+NJHV4z z&9)tjsYSR-zR;?GNnQ&Hd@eP(d|J;_Pdhm%C;`Gb%C@gP=lrCaa0xp&)9b6H zU@Zk7FkPaQKhtkzeEd__p!RJTM}2P_*I-t0q$4n@esnbAvks{CS_LN^1&K3cHbs+Q z`)LiiEI=vS#q-~vpAfsAMk{5j+>l>tBw`T-8qG3|)EYB9DAh~g!-uR*vaN<-yWo^J zw|FoCdQQf6H3Kl}y{1zhfmv<)MJ>ym@u*D&M_gEc&sN%fwG;kCExlqs6yL~Lc^_^` z(wh2g)9d7uOSsKG^x2l$R!CZD(&6p-jrvKmX8=IYegD4IKI!~v+n>*V&VT;--~Zge zyMHt1tas?rPyaeHKF{ABy&bmSxu^Eo$2q-tfAi75Z-wx~{ z8&KPS9`mYGZ+;S0W1F}@T+e-rJd2%L$Z`Fz z6E5fo2#m3TIii>N5gva88k9Ku%d>;pa=tbp6g|IkZ2v-5t59_!#a3A} zIhV=unx*+%b;T+6m#UVy6d>ypwJ33A#x4`Yiv45BFV(RkA?B#LXO|+?>lBpEh)v{w zfqkofxlpS6n(4SPAUUTYXu82jvAB-fRj6HshYSiAvtaU_H`)48q@m z#rc%=8yPkP^*X`t3tnb$i2WMd=%KBYG!Dnf1Zj>E=U1#X2Pn5Jx!n zm^~gZpM4HoqhOGTjlvMu=g?{LIgB?*c3Q&cE>^+I_qKgKLdJD+5+`6-h`XSFiTNmM za$$*5Q4x+Gsiui+GH{UC(S91yM)~46dRT5RKjwA^AV@%6?Y?A<1+8N2_GPeXLd#La6WmZ@pZF;;q zynrf-uUXEtyQgv$`;~L&wp8$c^vX)bTmdq9T1XX{v+TX5dvh3Oi?G#+{mUUY=hKL* zD0gIP&!m=W<(^4Q$yv`N@lM7wNmef0?D_jB7fg9G`C-qe0kra(+SQ898VD@fy>s=oNiFyE7<38C;WWqN}0ZYoWigk-=lyVH1<{h?;2vwrwVJIBoN?*?rlNBBn}d&f#GY7~5bE5}m`9W^s z%=Mwc@J%woKPTtk@Af&g6@ti)c$P2mBD_^%V1YS-_;>;s49pX>xPn_m&D8Vgc%j(m z3LFa=U~&{43VYMhn=~}u@=sU<2qT_lDH=l}DrC%vB`$^Jb_q^j4LF_bE+Sd^*lZT} zRY9aaQw1WZM%aTb|5CRR#og`f{v261tmn()E6C_`)cO-perkcm~&)Ra` lZ~_Ll1R*n=}0{eDl7 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 Z#vk;J$H$w;{}%uN|NqRvC4nmz0|2VG;ZFbn delta 12235 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 diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index def161171..d68a9132e 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -1583,3 +1583,8 @@ func getBaseFeeLowerBound(baseFee, factor types.BigInt) types.BigInt { return baseFeeLowerBound } + +type MpoolNonceAPI interface { + GetNonce(context.Context, address.Address, types.TipSetKey) (uint64, error) + GetActor(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) +} diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 5618ec603..b3fde5e75 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -10,7 +10,7 @@ import ( "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/messagesigner" + //"github.com/filecoin-project/lotus/chain/messagesigner" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -39,7 +39,7 @@ type mpoolProvider struct { sm *stmgr.StateManager ps *pubsub.PubSub - lite messagesigner.MpoolNonceAPI + lite MpoolNonceAPI } var _ Provider = (*mpoolProvider)(nil) @@ -48,7 +48,7 @@ func NewProvider(sm *stmgr.StateManager, ps *pubsub.PubSub) Provider { return &mpoolProvider{sm: sm, ps: ps} } -func NewProviderLite(sm *stmgr.StateManager, ps *pubsub.PubSub, noncer messagesigner.MpoolNonceAPI) Provider { +func NewProviderLite(sm *stmgr.StateManager, ps *pubsub.PubSub, noncer MpoolNonceAPI) Provider { return &mpoolProvider{sm: sm, ps: ps, lite: noncer} } diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index 67abf62d8..a110b04b3 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/modules/dtypes" ) @@ -24,11 +25,6 @@ const dsKeyMsgUUIDSet = "MsgUuidSet" var log = logging.Logger("messagesigner") -type MpoolNonceAPI interface { - GetNonce(context.Context, address.Address, types.TipSetKey) (uint64, error) - GetActor(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) -} - type MsgSigner interface { SignMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, cb func(*types.SignedMessage) error) (*types.SignedMessage, error) GetSignedMessage(ctx context.Context, uuid uuid.UUID) (*types.SignedMessage, error) @@ -47,13 +43,13 @@ type MsgSigner interface { type MessageSigner struct { wallet api.Wallet lk sync.Mutex - mpool MpoolNonceAPI + mpool messagepool.MpoolNonceAPI ds datastore.Batching } //var _ full.MsgSigner = &MessageSigner{} -func NewMessageSigner(wallet api.Wallet, mpool MpoolNonceAPI, ds dtypes.MetadataDS) *MessageSigner { +func NewMessageSigner(wallet api.Wallet, mpool messagepool.MpoolNonceAPI, ds dtypes.MetadataDS) *MessageSigner { ds = namespace.Wrap(ds, datastore.NewKey("/message-signer/")) return &MessageSigner{ wallet: wallet, diff --git a/chain/messagesigner/messagesigner_consensus.go b/chain/messagesigner/messagesigner_consensus.go index 8573ecd3d..1e90c27d8 100644 --- a/chain/messagesigner/messagesigner_consensus.go +++ b/chain/messagesigner/messagesigner_consensus.go @@ -10,6 +10,7 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/types" consensus "github.com/filecoin-project/lotus/lib/consensus/raft" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -22,7 +23,7 @@ type MessageSignerConsensus struct { func NewMessageSignerConsensus( wallet api.Wallet, - mpool MpoolNonceAPI, + mpool messagepool.MpoolNonceAPI, ds dtypes.MetadataDS, consensus *consensus.Consensus) *MessageSignerConsensus { diff --git a/cli/util/api.go b/cli/util/api.go index 8b46608e8..0b8da0ee9 100644 --- a/cli/util/api.go +++ b/cli/util/api.go @@ -4,8 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/filecoin-project/lotus/lib/retry" - "go.uber.org/atomic" "net/http" "net/url" "os" @@ -17,6 +15,7 @@ import ( "github.com/mitchellh/go-homedir" "github.com/urfave/cli/v2" + "go.uber.org/atomic" "golang.org/x/xerrors" "github.com/filecoin-project/go-jsonrpc" @@ -25,6 +24,7 @@ import ( "github.com/filecoin-project/lotus/api/client" "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/lib/retry" "github.com/filecoin-project/lotus/node/repo" ) diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index e69de29bb..a38b7c901 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -0,0 +1,302 @@ +[API] + # Binding address for the Lotus API + # + # type: string + # env var: LOTUS_API_LISTENADDRESS + #ListenAddress = "/ip4/127.0.0.1/tcp/1234/http" + + # type: string + # env var: LOTUS_API_REMOTELISTENADDRESS + #RemoteListenAddress = "" + + # type: Duration + # env var: LOTUS_API_TIMEOUT + #Timeout = "30s" + + +[Backup] + # When set to true disables metadata log (.lotus/kvlog). This can save disk + # space by reducing metadata redundancy. + # + # Note that in case of metadata corruption it might be much harder to recover + # your node if metadata log is disabled + # + # type: bool + # env var: LOTUS_BACKUP_DISABLEMETADATALOG + #DisableMetadataLog = true + + +[Logging] + [Logging.SubsystemLevels] + # env var: LOTUS_LOGGING_SUBSYSTEMLEVELS_EXAMPLE-SUBSYSTEM + #example-subsystem = "INFO" + + +[Libp2p] + # Binding address for the libp2p host - 0 means random port. + # Format: multiaddress; see https://multiformats.io/multiaddr/ + # + # type: []string + # env var: LOTUS_LIBP2P_LISTENADDRESSES + #ListenAddresses = ["/ip4/0.0.0.0/tcp/0", "/ip6/::/tcp/0"] + + # Addresses to explicitally announce to other peers. If not specified, + # all interface addresses are announced + # Format: multiaddress + # + # type: []string + # env var: LOTUS_LIBP2P_ANNOUNCEADDRESSES + #AnnounceAddresses = [] + + # Addresses to not announce + # Format: multiaddress + # + # type: []string + # env var: LOTUS_LIBP2P_NOANNOUNCEADDRESSES + #NoAnnounceAddresses = [] + + # When not disabled (default), lotus asks NAT devices (e.g., routers), to + # open up an external port and forward it to the port lotus is running on. + # When this works (i.e., when your router supports NAT port forwarding), + # it makes the local lotus node accessible from the public internet + # + # type: bool + # env var: LOTUS_LIBP2P_DISABLENATPORTMAP + #DisableNatPortMap = false + + # ConnMgrLow is the number of connections that the basic connection manager + # will trim down to. + # + # type: uint + # env var: LOTUS_LIBP2P_CONNMGRLOW + #ConnMgrLow = 150 + + # ConnMgrHigh is the number of connections that, when exceeded, will trigger + # a connection GC operation. Note: protected/recently formed connections don't + # count towards this limit. + # + # type: uint + # env var: LOTUS_LIBP2P_CONNMGRHIGH + #ConnMgrHigh = 180 + + # ConnMgrGrace is a time duration that new connections are immune from being + # closed by the connection manager. + # + # type: Duration + # env var: LOTUS_LIBP2P_CONNMGRGRACE + #ConnMgrGrace = "20s" + + +[Pubsub] + # Run the node in bootstrap-node mode + # + # type: bool + # env var: LOTUS_PUBSUB_BOOTSTRAPPER + #Bootstrapper = false + + # type: string + # env var: LOTUS_PUBSUB_REMOTETRACER + #RemoteTracer = "" + + +[Client] + # type: bool + # env var: LOTUS_CLIENT_USEIPFS + #UseIpfs = false + + # type: bool + # env var: LOTUS_CLIENT_IPFSONLINEMODE + #IpfsOnlineMode = false + + # type: string + # env var: LOTUS_CLIENT_IPFSMADDR + #IpfsMAddr = "" + + # type: bool + # env var: LOTUS_CLIENT_IPFSUSEFORRETRIEVAL + #IpfsUseForRetrieval = false + + # The maximum number of simultaneous data transfers between the client + # and storage providers for storage deals + # + # type: uint64 + # env var: LOTUS_CLIENT_SIMULTANEOUSTRANSFERSFORSTORAGE + #SimultaneousTransfersForStorage = 20 + + # The maximum number of simultaneous data transfers between the client + # and storage providers for retrieval deals + # + # type: uint64 + # env var: LOTUS_CLIENT_SIMULTANEOUSTRANSFERSFORRETRIEVAL + #SimultaneousTransfersForRetrieval = 20 + + # Require that retrievals perform no on-chain operations. Paid retrievals + # without existing payment channels with available funds will fail instead + # of automatically performing on-chain operations. + # + # type: bool + # env var: LOTUS_CLIENT_OFFCHAINRETRIEVAL + #OffChainRetrieval = false + + +[Wallet] + # type: string + # env var: LOTUS_WALLET_REMOTEBACKEND + #RemoteBackend = "" + + # type: bool + # env var: LOTUS_WALLET_ENABLELEDGER + #EnableLedger = false + + # type: bool + # env var: LOTUS_WALLET_DISABLELOCAL + #DisableLocal = false + + +[Fees] + # type: types.FIL + # env var: LOTUS_FEES_DEFAULTMAXFEE + #DefaultMaxFee = "0.07 FIL" + + +[Chainstore] + # type: bool + # env var: LOTUS_CHAINSTORE_ENABLESPLITSTORE + #EnableSplitstore = false + + [Chainstore.Splitstore] + # ColdStoreType specifies the type of the coldstore. + # It can be "universal" (default) or "discard" for discarding cold blocks. + # + # type: string + # env var: LOTUS_CHAINSTORE_SPLITSTORE_COLDSTORETYPE + #ColdStoreType = "universal" + + # HotStoreType specifies the type of the hotstore. + # Only currently supported value is "badger". + # + # type: string + # env var: LOTUS_CHAINSTORE_SPLITSTORE_HOTSTORETYPE + #HotStoreType = "badger" + + # MarkSetType specifies the type of the markset. + # It can be "map" for in memory marking or "badger" (default) for on-disk marking. + # + # type: string + # env var: LOTUS_CHAINSTORE_SPLITSTORE_MARKSETTYPE + #MarkSetType = "badger" + + # HotStoreMessageRetention specifies the retention policy for messages, in finalities beyond + # the compaction boundary; default is 0. + # + # type: uint64 + # env var: LOTUS_CHAINSTORE_SPLITSTORE_HOTSTOREMESSAGERETENTION + #HotStoreMessageRetention = 0 + + # HotStoreFullGCFrequency specifies how often to perform a full (moving) GC on the hotstore. + # A value of 0 disables, while a value 1 will do full GC in every compaction. + # Default is 20 (about once a week). + # + # type: uint64 + # env var: LOTUS_CHAINSTORE_SPLITSTORE_HOTSTOREFULLGCFREQUENCY + #HotStoreFullGCFrequency = 20 + + # EnableColdStoreAutoPrune turns on compaction of the cold store i.e. pruning + # where hotstore compaction occurs every finality epochs pruning happens every 3 finalities + # Default is false + # + # type: bool + # env var: LOTUS_CHAINSTORE_SPLITSTORE_ENABLECOLDSTOREAUTOPRUNE + #EnableColdStoreAutoPrune = false + + # ColdStoreFullGCFrequency specifies how often to performa a full (moving) GC on the coldstore. + # Only applies if auto prune is enabled. A value of 0 disables while a value of 1 will do + # full GC in every prune. + # Default is 7 (about once every a week) + # + # type: uint64 + # env var: LOTUS_CHAINSTORE_SPLITSTORE_COLDSTOREFULLGCFREQUENCY + #ColdStoreFullGCFrequency = 7 + + # ColdStoreRetention specifies the retention policy for data reachable from the chain, in + # finalities beyond the compaction boundary, default is 0, -1 retains everything + # + # type: int64 + # env var: LOTUS_CHAINSTORE_SPLITSTORE_COLDSTORERETENTION + #ColdStoreRetention = 0 + + +[Raft] + # config to enabled node cluster with raft consensus + # + # type: bool + # env var: LOTUS_RAFT_CLUSTERMODEENABLED + #ClusterModeEnabled = false + + # will shutdown libp2p host on shutdown. Useful for testing + # + # type: bool + # env var: LOTUS_RAFT_HOSTSHUTDOWN + #HostShutdown = false + + # A folder to store Raft's data. + # + # type: string + # env var: LOTUS_RAFT_DATAFOLDER + #DataFolder = "" + + # InitPeerset provides the list of initial cluster peers for new Raft + # peers (with no prior state). It is ignored when Raft was already + # initialized or when starting in staging mode. + # + # type: []peer.ID + # env var: LOTUS_RAFT_INITPEERSET + #InitPeerset = [] + + # LeaderTimeout specifies how long to wait for a leader before + # failing an operation. + # + # type: Duration + # env var: LOTUS_RAFT_WAITFORLEADERTIMEOUT + #WaitForLeaderTimeout = "15s" + + # NetworkTimeout specifies how long before a Raft network + # operation is timed out + # + # type: Duration + # env var: LOTUS_RAFT_NETWORKTIMEOUT + #NetworkTimeout = "1m40s" + + # CommitRetries specifies how many times we retry a failed commit until + # we give up. + # + # type: int + # env var: LOTUS_RAFT_COMMITRETRIES + #CommitRetries = 1 + + # How long to wait between retries + # + # type: Duration + # env var: LOTUS_RAFT_COMMITRETRYDELAY + #CommitRetryDelay = "200ms" + + # BackupsRotate specifies the maximum number of Raft's DataFolder + # copies that we keep as backups (renaming) after cleanup. + # + # type: int + # env var: LOTUS_RAFT_BACKUPSROTATE + #BackupsRotate = 6 + + # Namespace to use when writing keys to the datastore + # + # type: string + # env var: LOTUS_RAFT_DATASTORENAMESPACE + #DatastoreNamespace = "/raft" + + # Tracing enables propagation of contexts across binary boundaries. + # + # type: bool + # env var: LOTUS_RAFT_TRACING + #Tracing = false + + diff --git a/itests/raft_messagesigner_test.go b/itests/raft_messagesigner_test.go index acef88ed9..887cf7d31 100644 --- a/itests/raft_messagesigner_test.go +++ b/itests/raft_messagesigner_test.go @@ -42,7 +42,7 @@ func generatePrivKey() (*kit.Libp2p, error) { return &kit.Libp2p{PeerID: peerId, PrivKey: privkey}, nil } -func getRaftState(ctx context.Context, t *testing.T, node *kit.TestFullNode) *consensus.RaftState { +func getRaftState(ctx context.Context, t *testing.T, node *kit.TestFullNode) *api.RaftStateData { raftState, err := node.RaftState(ctx) require.NoError(t, err) //rstate := raftState.(*consensus.RaftState) @@ -51,7 +51,7 @@ func getRaftState(ctx context.Context, t *testing.T, node *kit.TestFullNode) *co func setup(ctx context.Context, t *testing.T, node0 *kit.TestFullNode, node1 *kit.TestFullNode, node2 *kit.TestFullNode, miner *kit.TestMiner) *kit.Ensemble { - //blockTime := 1000 * time.Millisecond + blockTime := 1 * time.Second pkey0, _ := generatePrivKey() pkey1, _ := generatePrivKey() @@ -61,8 +61,8 @@ func setup(ctx context.Context, t *testing.T, node0 *kit.TestFullNode, node1 *ki raftOps := kit.ConstructorOpts( node.Override(new(*gorpc.Client), modules.NewRPCClient), - node.Override(new(*config.ClusterRaftConfig), func() *config.ClusterRaftConfig { - cfg := config.DefaultClusterRaftConfig() + node.Override(new(*consensus.ClusterRaftConfig), func() *consensus.ClusterRaftConfig { + cfg := consensus.DefaultClusterRaftConfig() cfg.InitPeerset = initPeerSet return cfg }), @@ -304,35 +304,11 @@ func TestRaftStateLeaderDisconnectsMiner(t *testing.T) { peerToNode[n.Pkey.PeerID] = n } - //bal, err := node0.WalletBalance(ctx, node0.DefaultKey.Address) - //require.NoError(t, err) - - //msgHalfBal := &types.Message{ - // From: miner.OwnerKey.Address, - // To: node0.DefaultKey.Address, - // Value: big.Div(bal, big.NewInt(2)), - //} - //mu := uuid.New() - //smHalfBal, err := miner.FullNode.MpoolPushMessage(ctx, msgHalfBal, &api.MessageSendSpec{ - // MsgUuid: mu, - //}) - //require.NoError(t, err) - //mLookup, err := node0.StateWaitMsg(ctx, smHalfBal.Cid(), 3, api.LookbackNoLimit, true) - //require.NoError(t, err) - //require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) - // - //rstate0 := getRaftState(ctx, t, &node0) - //rstate1 := getRaftState(ctx, t, &node1) - //rstate2 := getRaftState(ctx, t, &node2) - // - //require.True(t, reflect.DeepEqual(rstate0, rstate1)) - //require.True(t, reflect.DeepEqual(rstate0, rstate2)) - - // Take leader node down - leader, err := node1.RaftLeader(ctx) + leader, err := node0.RaftLeader(ctx) require.NoError(t, err) leaderNode := peerToNode[leader] + // Take leader node down err = leaderNode.Stop(ctx) require.NoError(t, err) oldLeaderNode := leaderNode @@ -351,11 +327,6 @@ func TestRaftStateLeaderDisconnectsMiner(t *testing.T) { require.NotEqual(t, newLeader, leader) leaderNode = peerToNode[newLeader] - fmt.Println("New leader: ", newLeader) - - //err = node0.Stop(ctx) - //require.NoError(t, err) - msg2 := &types.Message{ From: miner.OwnerKey.Address, To: node0.DefaultKey.Address, @@ -373,8 +344,6 @@ func TestRaftStateLeaderDisconnectsMiner(t *testing.T) { require.NoError(t, err) require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) - fmt.Println("!!!!!!!!!!!!!!!!TEST FINISHED!!!!!!!!!!!!!!!!!!!") - rstate := getRaftState(ctx, t, leaderNode) for _, n := range nodes { @@ -420,24 +389,16 @@ func TestLeaderDisconnectsCheckMsgStateOnNewLeader(t *testing.T) { }) require.NoError(t, err) - // - //rstate0 := getRaftState(ctx, t, &node0) - //rstate1 := getRaftState(ctx, t, &node1) - //rstate2 := getRaftState(ctx, t, &node2) - // - //require.True(t, reflect.DeepEqual(rstate0, rstate1)) - //require.True(t, reflect.DeepEqual(rstate0, rstate2)) - - // Take leader node down - leader, err := node1.RaftLeader(ctx) + leader, err := node0.RaftLeader(ctx) require.NoError(t, err) leaderNode := peerToNode[leader] + // Take leader node down err = leaderNode.Stop(ctx) require.NoError(t, err) oldLeaderNode := leaderNode - //time.Sleep(5 * time.Second) + time.Sleep(5 * time.Second) newLeader := leader for _, n := range nodes { @@ -451,17 +412,10 @@ func TestLeaderDisconnectsCheckMsgStateOnNewLeader(t *testing.T) { require.NotEqual(t, newLeader, leader) leaderNode = peerToNode[newLeader] - fmt.Println("New leader: ", newLeader) - mLookup, err := leaderNode.StateWaitMsg(ctx, smHalfBal.Cid(), 3, api.LookbackNoLimit, true) require.NoError(t, err) require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) - //err = node0.Stop(ctx) - //require.NoError(t, err) - - fmt.Println("!!!!!!!!!!!!!!!!TEST FINISHED!!!!!!!!!!!!!!!!!!!") - rstate := getRaftState(ctx, t, leaderNode) for _, n := range nodes { @@ -471,3 +425,53 @@ func TestLeaderDisconnectsCheckMsgStateOnNewLeader(t *testing.T) { } } } + +func TestChainStoreSync(t *testing.T) { + + kit.QuietMiningLogs() + ctx := context.Background() + + var ( + node0 kit.TestFullNode + node1 kit.TestFullNode + node2 kit.TestFullNode + miner kit.TestMiner + ) + + nodes := []*kit.TestFullNode{&node0, &node1, &node2} + + setup(ctx, t, &node0, &node1, &node2, &miner) + + peerToNode := make(map[peer.ID]*kit.TestFullNode) + for _, n := range nodes { + peerToNode[n.Pkey.PeerID] = n + } + + bal, err := node0.WalletBalance(ctx, node0.DefaultKey.Address) + require.NoError(t, err) + + leader, err := node0.RaftLeader(ctx) + require.NoError(t, err) + leaderNode := peerToNode[leader] + + msgHalfBal := &types.Message{ + From: miner.OwnerKey.Address, + To: node0.DefaultKey.Address, + Value: big.Div(bal, big.NewInt(2)), + } + mu := uuid.New() + smHalfBal, err := miner.FullNode.MpoolPushMessage(ctx, msgHalfBal, &api.MessageSendSpec{ + MsgUuid: mu, + }) + require.NoError(t, err) + + for _, n := range nodes { + fmt.Println(n != leaderNode) + if n != leaderNode { + mLookup, err := n.StateWaitMsg(ctx, smHalfBal.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + //break + } + } +} diff --git a/lib/consensus/raft/config.go b/lib/consensus/raft/config.go index 8b0070403..3153d2a33 100644 --- a/lib/consensus/raft/config.go +++ b/lib/consensus/raft/config.go @@ -1,9 +1,11 @@ package consensus import ( + "io/ioutil" "time" hraft "github.com/hashicorp/raft" + "github.com/libp2p/go-libp2p/core/peer" "golang.org/x/xerrors" "github.com/filecoin-project/lotus/node/config" @@ -25,45 +27,88 @@ var ( DefaultDatastoreNamespace = "/r" // from "/raft" ) -// Config allows to configure the Raft Consensus component for ipfs-cluster. -// The component's configuration section is represented by ConfigJSON. -// Config implements the ComponentConfig interface. -//type Config struct { -// //config.Saver -// // -// //// will shutdown libp2p host on shutdown. Useful for testing -// hostShutdown bool -// -// // A folder to store Raft's data. -// DataFolder string -// -// // InitPeerset provides the list of initial cluster peers for new Raft -// // peers (with no prior state). It is ignored when Raft was already -// // initialized or when starting in staging mode. -// InitPeerset []peer.ID -// // LeaderTimeout specifies how long to wait for a leader before -// // failing an operation. -// WaitForLeaderTimeout time.Duration -// // NetworkTimeout specifies how long before a Raft network -// // operation is timed out -// NetworkTimeout time.Duration -// // CommitRetries specifies how many times we retry a failed commit until -// // we give up. -// CommitRetries int -// // How long to wait between retries -// CommitRetryDelay time.Duration -// // BackupsRotate specifies the maximum number of Raft's DataFolder -// // copies that we keep as backups (renaming) after cleanup. -// BackupsRotate int -// // Namespace to use when writing keys to the datastore -// DatastoreNamespace string -// -// // A Hashicorp Raft's configuration object. -// RaftConfig *hraft.Config -// -// // Tracing enables propagation of contexts across binary boundaries. -// Tracing bool -//} +// ClusterRaftConfig allows to configure the Raft Consensus component for the node cluster. +type ClusterRaftConfig struct { + // config to enabled node cluster with raft consensus + ClusterModeEnabled bool + // will shutdown libp2p host on shutdown. Useful for testing + HostShutdown bool + // A folder to store Raft's data. + DataFolder string + // InitPeerset provides the list of initial cluster peers for new Raft + // peers (with no prior state). It is ignored when Raft was already + // initialized or when starting in staging mode. + InitPeerset []peer.ID + // LeaderTimeout specifies how long to wait for a leader before + // failing an operation. + WaitForLeaderTimeout time.Duration + // NetworkTimeout specifies how long before a Raft network + // operation is timed out + NetworkTimeout time.Duration + // CommitRetries specifies how many times we retry a failed commit until + // we give up. + CommitRetries int + // How long to wait between retries + CommitRetryDelay time.Duration + // BackupsRotate specifies the maximum number of Raft's DataFolder + // copies that we keep as backups (renaming) after cleanup. + BackupsRotate int + // Namespace to use when writing keys to the datastore + DatastoreNamespace string + + // A Hashicorp Raft's configuration object. + RaftConfig *hraft.Config + + // Tracing enables propagation of contexts across binary boundaries. + Tracing bool +} + +func DefaultClusterRaftConfig() *ClusterRaftConfig { + var cfg ClusterRaftConfig + cfg.DataFolder = "" // empty so it gets omitted + cfg.InitPeerset = []peer.ID{} + cfg.WaitForLeaderTimeout = DefaultWaitForLeaderTimeout + cfg.NetworkTimeout = DefaultNetworkTimeout + cfg.CommitRetries = DefaultCommitRetries + cfg.CommitRetryDelay = DefaultCommitRetryDelay + cfg.BackupsRotate = DefaultBackupsRotate + cfg.DatastoreNamespace = DefaultDatastoreNamespace + cfg.RaftConfig = hraft.DefaultConfig() + + // These options are imposed over any Default Raft Config. + cfg.RaftConfig.ShutdownOnRemove = false + cfg.RaftConfig.LocalID = "will_be_set_automatically" + + // Set up logging + cfg.RaftConfig.LogOutput = ioutil.Discard + //cfg.RaftConfig.Logger = &hcLogToLogger{} + return &cfg +} + +func NewClusterRaftConfig(userRaftConfig *config.UserRaftConfig) *ClusterRaftConfig { + var cfg ClusterRaftConfig + cfg.DataFolder = userRaftConfig.DataFolder + cfg.InitPeerset = userRaftConfig.InitPeerset + cfg.WaitForLeaderTimeout = time.Duration(userRaftConfig.WaitForLeaderTimeout) + cfg.NetworkTimeout = time.Duration(userRaftConfig.NetworkTimeout) + cfg.CommitRetries = userRaftConfig.CommitRetries + cfg.CommitRetryDelay = time.Duration(userRaftConfig.CommitRetryDelay) + cfg.BackupsRotate = userRaftConfig.BackupsRotate + cfg.DatastoreNamespace = userRaftConfig.DatastoreNamespace + + // Keep this to be default hraft config for now + cfg.RaftConfig = hraft.DefaultConfig() + + // These options are imposed over any Default Raft Config. + cfg.RaftConfig.ShutdownOnRemove = false + cfg.RaftConfig.LocalID = "will_be_set_automatically" + + // Set up logging + cfg.RaftConfig.LogOutput = ioutil.Discard + //cfg.RaftConfig.Logger = &hcLogToLogger{} + return &cfg + +} // ConfigJSON represents a human-friendly Config // object which can be saved to JSON. Most configuration keys are converted @@ -145,7 +190,7 @@ var ( //// Validate checks that this configuration has working values, //// at least in appearance. -func ValidateConfig(cfg *config.ClusterRaftConfig) error { +func ValidateConfig(cfg *ClusterRaftConfig) error { if cfg.RaftConfig == nil { return xerrors.Errorf("no hashicorp/raft.Config") } diff --git a/lib/consensus/raft/consensus.go b/lib/consensus/raft/consensus.go index 76a01f426..464214308 100644 --- a/lib/consensus/raft/consensus.go +++ b/lib/consensus/raft/consensus.go @@ -4,7 +4,6 @@ package consensus import ( "context" - "encoding/json" "errors" "fmt" "sort" @@ -15,8 +14,9 @@ import ( addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/config" //ds "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" @@ -29,67 +29,21 @@ import ( var logger = logging.Logger("raft") -type NonceMapType map[addr.Address]uint64 -type MsgUuidMapType map[uuid.UUID]*types.SignedMessage +//type NonceMapType map[addr.Address]uint64 +//type MsgUuidMapType map[uuid.UUID]*types.SignedMessage type RaftState struct { - NonceMap NonceMapType - MsgUuids MsgUuidMapType + NonceMap api.NonceMapType + MsgUuids api.MsgUuidMapType + Mpool *messagepool.MessagePool } -func newRaftState() *RaftState { - return &RaftState{NonceMap: make(map[addr.Address]uint64), - MsgUuids: make(map[uuid.UUID]*types.SignedMessage)} -} - -func (n *NonceMapType) MarshalJSON() ([]byte, error) { - marshalled := make(map[string]uint64) - for a, n := range *n { - marshalled[a.String()] = n +func newRaftState(mpool *messagepool.MessagePool) *RaftState { + return &RaftState{ + NonceMap: make(map[addr.Address]uint64), + MsgUuids: make(map[uuid.UUID]*types.SignedMessage), + Mpool: mpool, } - return json.Marshal(marshalled) -} - -func (n *NonceMapType) UnmarshalJSON(b []byte) error { - unmarshalled := make(map[string]uint64) - err := json.Unmarshal(b, &unmarshalled) - if err != nil { - return err - } - *n = make(map[addr.Address]uint64) - for saddr, nonce := range unmarshalled { - a, err := addr.NewFromString(saddr) - if err != nil { - return err - } - (*n)[a] = nonce - } - return nil -} - -func (m *MsgUuidMapType) MarshalJSON() ([]byte, error) { - marshalled := make(map[string]*types.SignedMessage) - for u, msg := range *m { - marshalled[u.String()] = msg - } - return json.Marshal(marshalled) -} - -func (m *MsgUuidMapType) UnmarshalJSON(b []byte) error { - unmarshalled := make(map[string]*types.SignedMessage) - err := json.Unmarshal(b, &unmarshalled) - if err != nil { - return err - } - *m = make(map[uuid.UUID]*types.SignedMessage) - for suid, msg := range unmarshalled { - u, err := uuid.Parse(suid) - if err != nil { - return err - } - (*m)[u] = msg - } - return nil } type ConsensusOp struct { @@ -103,6 +57,7 @@ func (c ConsensusOp) ApplyTo(state consensus.State) (consensus.State, error) { s := state.(*RaftState) s.NonceMap[c.Addr] = c.Nonce s.MsgUuids[c.Uuid] = c.SignedMsg + s.Mpool.Add(context.TODO(), c.SignedMsg) return s, nil } @@ -114,7 +69,7 @@ var _ consensus.Op = &ConsensusOp{} type Consensus struct { ctx context.Context cancel func() - config *config.ClusterRaftConfig + config *ClusterRaftConfig host host.Host @@ -141,7 +96,7 @@ type Consensus struct { // // The staging parameter controls if the Raft peer should start in // staging mode (used when joining a new Raft peerset with other peers). -func NewConsensus(host host.Host, cfg *config.ClusterRaftConfig, staging bool) (*Consensus, error) { +func NewConsensus(host host.Host, cfg *ClusterRaftConfig, mpool *messagepool.MessagePool, staging bool) (*Consensus, error) { err := ValidateConfig(cfg) if err != nil { return nil, err @@ -150,7 +105,7 @@ func NewConsensus(host host.Host, cfg *config.ClusterRaftConfig, staging bool) ( ctx, cancel := context.WithCancel(context.Background()) logger.Debug("starting Consensus and waiting for a leader...") - state := newRaftState() + state := newRaftState(mpool) consensus := libp2praft.NewOpLog(state, &ConsensusOp{}) @@ -183,12 +138,13 @@ func NewConsensus(host host.Host, cfg *config.ClusterRaftConfig, staging bool) ( } func NewConsensusWithRPCClient(staging bool) func(host host.Host, - cfg *config.ClusterRaftConfig, + cfg *ClusterRaftConfig, rpcClient *rpc.Client, + mpool *messagepool.MessagePool, ) (*Consensus, error) { - return func(host host.Host, cfg *config.ClusterRaftConfig, rpcClient *rpc.Client) (*Consensus, error) { - cc, err := NewConsensus(host, cfg, staging) + return func(host host.Host, cfg *ClusterRaftConfig, rpcClient *rpc.Client, mpool *messagepool.MessagePool) (*Consensus, error) { + cc, err := NewConsensus(host, cfg, mpool, staging) if err != nil { return nil, err } @@ -506,7 +462,7 @@ func (cc *Consensus) State(ctx context.Context) (*RaftState, error) { st, err := cc.consensus.GetLogHead() if err == libp2praft.ErrNoState { - return newRaftState(), nil + return newRaftState(nil), nil } if err != nil { diff --git a/lib/consensus/raft/raft.go b/lib/consensus/raft/raft.go index 6b6042744..245ae168d 100644 --- a/lib/consensus/raft/raft.go +++ b/lib/consensus/raft/raft.go @@ -12,8 +12,6 @@ import ( p2praft "github.com/libp2p/go-libp2p-raft" host "github.com/libp2p/go-libp2p/core/host" peer "github.com/libp2p/go-libp2p/core/peer" - - "github.com/filecoin-project/lotus/node/config" ) // ErrWaitingForSelf is returned when we are waiting for ourselves to depart @@ -44,7 +42,7 @@ type raftWrapper struct { ctx context.Context cancel context.CancelFunc raft *hraft.Raft - config *config.ClusterRaftConfig + config *ClusterRaftConfig host host.Host serverConfig hraft.Configuration transport *hraft.NetworkTransport @@ -60,7 +58,7 @@ type raftWrapper struct { // to make sure the raft instance is usable. func newRaftWrapper( host host.Host, - cfg *config.ClusterRaftConfig, + cfg *ClusterRaftConfig, fsm hraft.FSM, staging bool, ) (*raftWrapper, error) { diff --git a/node/builder_chain.go b/node/builder_chain.go index 338f7e7eb..3cb8af830 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -143,7 +143,7 @@ var ChainNode = Options( // Lite node API ApplyIf(isLiteNode, Override(new(messagepool.Provider), messagepool.NewProviderLite), - Override(new(messagesigner.MpoolNonceAPI), From(new(modules.MpoolNonceAPI))), + Override(new(messagepool.MpoolNonceAPI), From(new(modules.MpoolNonceAPI))), Override(new(full.ChainModuleAPI), From(new(api.Gateway))), Override(new(full.GasModuleAPI), From(new(api.Gateway))), Override(new(full.MpoolModuleAPI), From(new(api.Gateway))), @@ -154,7 +154,7 @@ var ChainNode = Options( // Full node API / service startup ApplyIf(isFullNode, Override(new(messagepool.Provider), messagepool.NewProvider), - Override(new(messagesigner.MpoolNonceAPI), From(new(*messagepool.MessagePool))), + Override(new(messagepool.MpoolNonceAPI), From(new(*messagepool.MessagePool))), Override(new(full.ChainModuleAPI), From(new(full.ChainModule))), Override(new(full.GasModuleAPI), From(new(full.GasModule))), Override(new(full.MpoolModuleAPI), From(new(full.MpoolModule))), diff --git a/node/config/def.go b/node/config/def.go index dc19209bb..36fb05c95 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -2,12 +2,10 @@ package config import ( "encoding" - "io/ioutil" "os" "strconv" "time" - hraft "github.com/hashicorp/raft" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" @@ -102,7 +100,7 @@ func DefaultFullNode() *FullNode { ColdStoreFullGCFrequency: 7, }, }, - Raft: *DefaultClusterRaftConfig(), + Raft: *DefaultUserRaftConfig(), } } @@ -290,8 +288,30 @@ var ( DefaultDatastoreNamespace = "/raft" ) -func DefaultClusterRaftConfig() *ClusterRaftConfig { - var cfg ClusterRaftConfig +//func DefaultClusterRaftConfig() *ClusterRaftConfig { +// var cfg ClusterRaftConfig +// cfg.DataFolder = "" // empty so it gets omitted +// cfg.InitPeerset = []peer.ID{} +// cfg.WaitForLeaderTimeout = Duration(DefaultWaitForLeaderTimeout) +// cfg.NetworkTimeout = Duration(DefaultNetworkTimeout) +// cfg.CommitRetries = DefaultCommitRetries +// cfg.CommitRetryDelay = Duration(DefaultCommitRetryDelay) +// cfg.BackupsRotate = DefaultBackupsRotate +// cfg.DatastoreNamespace = DefaultDatastoreNamespace +// cfg.RaftConfig = hraft.DefaultConfig() +// +// // These options are imposed over any Default Raft Config. +// cfg.RaftConfig.ShutdownOnRemove = false +// cfg.RaftConfig.LocalID = "will_be_set_automatically" +// +// // Set up logging +// cfg.RaftConfig.LogOutput = ioutil.Discard +// //cfg.RaftConfig.Logger = &hcLogToLogger{} +// return &cfg +//} + +func DefaultUserRaftConfig() *UserRaftConfig { + var cfg UserRaftConfig cfg.DataFolder = "" // empty so it gets omitted cfg.InitPeerset = []peer.ID{} cfg.WaitForLeaderTimeout = Duration(DefaultWaitForLeaderTimeout) @@ -300,14 +320,6 @@ func DefaultClusterRaftConfig() *ClusterRaftConfig { cfg.CommitRetryDelay = Duration(DefaultCommitRetryDelay) cfg.BackupsRotate = DefaultBackupsRotate cfg.DatastoreNamespace = DefaultDatastoreNamespace - cfg.RaftConfig = hraft.DefaultConfig() - // These options are imposed over any Default Raft Config. - cfg.RaftConfig.ShutdownOnRemove = false - cfg.RaftConfig.LocalID = "will_be_set_automatically" - - // Set up logging - cfg.RaftConfig.LogOutput = ioutil.Discard - //cfg.RaftConfig.Logger = &hcLogToLogger{} return &cfg } diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 7f295ad3f..ca62cd4d5 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -117,86 +117,6 @@ without existing payment channels with available funds will fail instead of automatically performing on-chain operations.`, }, }, - "ClusterRaftConfig": []DocField{ - { - Name: "ClusterModeEnabled", - Type: "bool", - - Comment: `config to enabled node cluster with raft consensus`, - }, - { - Name: "HostShutdown", - Type: "bool", - - Comment: `will shutdown libp2p host on shutdown. Useful for testing`, - }, - { - Name: "DataFolder", - Type: "string", - - Comment: `A folder to store Raft's data.`, - }, - { - Name: "InitPeerset", - Type: "[]peer.ID", - - Comment: `InitPeerset provides the list of initial cluster peers for new Raft -peers (with no prior state). It is ignored when Raft was already -initialized or when starting in staging mode.`, - }, - { - Name: "WaitForLeaderTimeout", - Type: "Duration", - - Comment: `LeaderTimeout specifies how long to wait for a leader before -failing an operation.`, - }, - { - Name: "NetworkTimeout", - Type: "Duration", - - Comment: `NetworkTimeout specifies how long before a Raft network -operation is timed out`, - }, - { - Name: "CommitRetries", - Type: "int", - - Comment: `CommitRetries specifies how many times we retry a failed commit until -we give up.`, - }, - { - Name: "CommitRetryDelay", - Type: "Duration", - - Comment: `How long to wait between retries`, - }, - { - Name: "BackupsRotate", - Type: "int", - - Comment: `BackupsRotate specifies the maximum number of Raft's DataFolder -copies that we keep as backups (renaming) after cleanup.`, - }, - { - Name: "DatastoreNamespace", - Type: "string", - - Comment: `Namespace to use when writing keys to the datastore`, - }, - { - Name: "RaftConfig", - Type: "*hraft.Config", - - Comment: `A Hashicorp Raft's configuration object.`, - }, - { - Name: "Tracing", - Type: "bool", - - Comment: `Tracing enables propagation of contexts across binary boundaries.`, - }, - }, "Common": []DocField{ { Name: "API", @@ -456,7 +376,7 @@ see https://lotus.filecoin.io/storage-providers/advanced-configurations/market/# }, { Name: "Raft", - Type: "ClusterRaftConfig", + Type: "UserRaftConfig", Comment: ``, }, @@ -1303,6 +1223,80 @@ finalities beyond the compaction boundary, default is 0, -1 retains everything`, Comment: ``, }, }, + "UserRaftConfig": []DocField{ + { + Name: "ClusterModeEnabled", + Type: "bool", + + Comment: `config to enabled node cluster with raft consensus`, + }, + { + Name: "HostShutdown", + Type: "bool", + + Comment: `will shutdown libp2p host on shutdown. Useful for testing`, + }, + { + Name: "DataFolder", + Type: "string", + + Comment: `A folder to store Raft's data.`, + }, + { + Name: "InitPeerset", + Type: "[]peer.ID", + + Comment: `InitPeerset provides the list of initial cluster peers for new Raft +peers (with no prior state). It is ignored when Raft was already +initialized or when starting in staging mode.`, + }, + { + Name: "WaitForLeaderTimeout", + Type: "Duration", + + Comment: `LeaderTimeout specifies how long to wait for a leader before +failing an operation.`, + }, + { + Name: "NetworkTimeout", + Type: "Duration", + + Comment: `NetworkTimeout specifies how long before a Raft network +operation is timed out`, + }, + { + Name: "CommitRetries", + Type: "int", + + Comment: `CommitRetries specifies how many times we retry a failed commit until +we give up.`, + }, + { + Name: "CommitRetryDelay", + Type: "Duration", + + Comment: `How long to wait between retries`, + }, + { + Name: "BackupsRotate", + Type: "int", + + Comment: `BackupsRotate specifies the maximum number of Raft's DataFolder +copies that we keep as backups (renaming) after cleanup.`, + }, + { + Name: "DatastoreNamespace", + Type: "string", + + Comment: `Namespace to use when writing keys to the datastore`, + }, + { + Name: "Tracing", + Type: "bool", + + Comment: `Tracing enables propagation of contexts across binary boundaries.`, + }, + }, "Wallet": []DocField{ { Name: "RemoteBackend", diff --git a/node/config/types.go b/node/config/types.go index 534115a45..1b0963c55 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -1,7 +1,6 @@ package config import ( - hraft "github.com/hashicorp/raft" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" @@ -29,7 +28,7 @@ type FullNode struct { Wallet Wallet Fees FeeConfig Chainstore Chainstore - Raft ClusterRaftConfig + Raft UserRaftConfig } // // Common @@ -612,8 +611,43 @@ type FeeConfig struct { DefaultMaxFee types.FIL } -// ClusterRaftConfig allows to configure the Raft Consensus component for the node cluster. -type ClusterRaftConfig struct { +//// ClusterRaftConfig allows to configure the Raft Consensus component for the node cluster. +//type ClusterRaftConfig struct { +// // config to enabled node cluster with raft consensus +// ClusterModeEnabled bool +// // will shutdown libp2p host on shutdown. Useful for testing +// HostShutdown bool +// // A folder to store Raft's data. +// DataFolder string +// // InitPeerset provides the list of initial cluster peers for new Raft +// // peers (with no prior state). It is ignored when Raft was already +// // initialized or when starting in staging mode. +// InitPeerset []peer.ID +// // LeaderTimeout specifies how long to wait for a leader before +// // failing an operation. +// WaitForLeaderTimeout Duration +// // NetworkTimeout specifies how long before a Raft network +// // operation is timed out +// NetworkTimeout Duration +// // CommitRetries specifies how many times we retry a failed commit until +// // we give up. +// CommitRetries int +// // How long to wait between retries +// CommitRetryDelay Duration +// // BackupsRotate specifies the maximum number of Raft's DataFolder +// // copies that we keep as backups (renaming) after cleanup. +// BackupsRotate int +// // Namespace to use when writing keys to the datastore +// DatastoreNamespace string +// +// // A Hashicorp Raft's configuration object. +// RaftConfig *hraft.Config +// +// // Tracing enables propagation of contexts across binary boundaries. +// Tracing bool +//} + +type UserRaftConfig struct { // config to enabled node cluster with raft consensus ClusterModeEnabled bool // will shutdown libp2p host on shutdown. Useful for testing @@ -642,7 +676,7 @@ type ClusterRaftConfig struct { DatastoreNamespace string // A Hashicorp Raft's configuration object. - RaftConfig *hraft.Config + //RaftConfig *hraft.Config // Tracing enables propagation of contexts across binary boundaries. Tracing bool diff --git a/node/impl/full.go b/node/impl/full.go index dc2568de3..e0a765806 100644 --- a/node/impl/full.go +++ b/node/impl/full.go @@ -9,7 +9,8 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" - consensus2 "github.com/filecoin-project/lotus/lib/consensus/raft" + + //consensus2 "github.com/filecoin-project/lotus/lib/consensus/raft" "github.com/filecoin-project/lotus/node/impl/client" "github.com/filecoin-project/lotus/node/impl/common" "github.com/filecoin-project/lotus/node/impl/full" @@ -119,7 +120,7 @@ func (n *FullNodeAPI) NodeStatus(ctx context.Context, inclChainStatus bool) (sta return status, nil } -func (n *FullNodeAPI) RaftState(ctx context.Context) (*consensus2.RaftState, error) { +func (n *FullNodeAPI) RaftState(ctx context.Context) (*api.RaftStateData, error) { return n.RaftAPI.GetRaftState(ctx) } diff --git a/node/impl/full/raft.go b/node/impl/full/raft.go index 93e0d98b2..b5019c9ab 100644 --- a/node/impl/full/raft.go +++ b/node/impl/full/raft.go @@ -2,12 +2,13 @@ package full import ( "context" + "github.com/libp2p/go-libp2p/core/peer" "go.uber.org/fx" "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/messagesigner" - consensus "github.com/filecoin-project/lotus/lib/consensus/raft" ) type RaftAPI struct { @@ -16,11 +17,15 @@ type RaftAPI struct { MessageSigner *messagesigner.MessageSignerConsensus `optional:"true"` } -func (r *RaftAPI) GetRaftState(ctx context.Context) (*consensus.RaftState, error) { +func (r *RaftAPI) GetRaftState(ctx context.Context) (*api.RaftStateData, error) { if r.MessageSigner == nil { return nil, xerrors.Errorf("Raft consensus not enabled. Please check your configuration") } - return r.MessageSigner.GetRaftState(ctx) + raftState, err := r.MessageSigner.GetRaftState(ctx) + if err != nil { + return nil, err + } + return &api.RaftStateData{NonceMap: raftState.NonceMap, MsgUuids: raftState.MsgUuids}, nil } func (r *RaftAPI) Leader(ctx context.Context) (peer.ID, error) { diff --git a/node/modules/mpoolnonceapi.go b/node/modules/mpoolnonceapi.go index 00e704727..393bee32f 100644 --- a/node/modules/mpoolnonceapi.go +++ b/node/modules/mpoolnonceapi.go @@ -9,7 +9,7 @@ import ( "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/messagesigner" + "github.com/filecoin-project/lotus/chain/messagepool" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/impl/full" ) @@ -104,4 +104,4 @@ func (a *MpoolNonceAPI) GetActor(ctx context.Context, addr address.Address, tsk return act, nil } -var _ messagesigner.MpoolNonceAPI = (*MpoolNonceAPI)(nil) +var _ messagepool.MpoolNonceAPI = (*MpoolNonceAPI)(nil)