From 01b6ddbbd33ad504d8e2115ee9467ce9d22c226a Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Fri, 31 Jan 2020 15:51:15 -0800 Subject: [PATCH] add a test to ensure there are no off-by-one errors in fork activation logic --- chain/actors/actor_init.go | 9 +- chain/actors/actors.go | 11 ++ chain/gen/gen.go | 34 ++++-- chain/stmgr/forks.go | 63 ++++++---- chain/stmgr/forks_test.go | 231 +++++++++++++++++++++++++++++++++++++ chain/stmgr/stmgr.go | 9 +- chain/vm/invoker.go | 22 ++-- chain/vm/vm.go | 6 +- 8 files changed, 336 insertions(+), 49 deletions(-) create mode 100644 chain/stmgr/forks_test.go diff --git a/chain/actors/actor_init.go b/chain/actors/actor_init.go index f71704c5c..c91fc8d70 100644 --- a/chain/actors/actor_init.go +++ b/chain/actors/actor_init.go @@ -27,6 +27,8 @@ const ( GasCreateActor = 100 ) +var BuiltInActors map[cid.Cid]bool + func init() { n, err := cbor.WrapObject(map[string]string{}, mh.SHA2_256, -1) @@ -160,12 +162,7 @@ func (ia InitActor) Exec(act *types.Actor, vmctx types.VMContext, p *ExecParams) } func IsBuiltinActor(code cid.Cid) bool { - switch code { - case StorageMarketCodeCid, StoragePowerCodeCid, StorageMinerCodeCid, StorageMiner2CodeCid, AccountCodeCid, InitCodeCid, MultisigCodeCid, PaymentChannelCodeCid: - return true - default: - return false - } + return BuiltInActors[code] } func IsSingletonActor(code cid.Cid) bool { diff --git a/chain/actors/actors.go b/chain/actors/actors.go index cc183046e..28f4c4ad7 100644 --- a/chain/actors/actors.go +++ b/chain/actors/actors.go @@ -51,4 +51,15 @@ func init() { MultisigCodeCid = mustSum("fil/1/multisig") InitCodeCid = mustSum("fil/1/init") PaymentChannelCodeCid = mustSum("fil/1/paych") + + BuiltInActors = map[cid.Cid]bool{ + StorageMarketCodeCid: true, + StoragePowerCodeCid: true, + StorageMinerCodeCid: true, + StorageMiner2CodeCid: true, + AccountCodeCid: true, + InitCodeCid: true, + MultisigCodeCid: true, + PaymentChannelCodeCid: true, + } } diff --git a/chain/gen/gen.go b/chain/gen/gen.go index bf0c7a2b9..066ec918a 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -6,10 +6,11 @@ import ( "crypto/sha256" "encoding/binary" "fmt" - "github.com/filecoin-project/lotus/chain/vm" "io/ioutil" "sync/atomic" + "github.com/filecoin-project/lotus/chain/vm" + ffi "github.com/filecoin-project/filecoin-ffi" sectorbuilder "github.com/filecoin-project/go-sectorbuilder" @@ -56,6 +57,8 @@ type ChainGen struct { Timestamper func(*types.TipSet, uint64) uint64 + GetMessages func(*ChainGen) ([]*types.SignedMessage, error) + w *wallet.Wallet eppProvs map[address.Address]ElectionPoStProver @@ -210,10 +213,11 @@ func NewGenerator() (*ChainGen, error) { genesis: genb.Genesis, w: w, - Miners: minercfg.MinerAddrs, - eppProvs: mgen, - banker: banker, - receivers: receievers, + GetMessages: getRandomMessages, + Miners: minercfg.MinerAddrs, + eppProvs: mgen, + banker: banker, + receivers: receievers, CurTipset: gents, @@ -224,6 +228,14 @@ func NewGenerator() (*ChainGen, error) { return gen, nil } +func (cg *ChainGen) SetStateManager(sm *stmgr.StateManager) { + cg.sm = sm +} + +func (cg *ChainGen) ChainStore() *store.ChainStore { + return cg.cs +} + func (cg *ChainGen) Genesis() *types.BlockHeader { return cg.genesis } @@ -295,7 +307,7 @@ func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) { func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address) (*MinedTipSet, error) { var blks []*types.FullBlock - msgs, err := cg.getRandomMessages() + msgs, err := cg.GetMessages(cg) if err != nil { return nil, xerrors.Errorf("get random messages: %w", err) } @@ -359,7 +371,15 @@ func (cg *ChainGen) ResyncBankerNonce(ts *types.TipSet) error { return nil } -func (cg *ChainGen) getRandomMessages() ([]*types.SignedMessage, error) { +func (cg *ChainGen) Banker() address.Address { + return cg.banker +} + +func (cg *ChainGen) Wallet() *wallet.Wallet { + return cg.w +} + +func getRandomMessages(cg *ChainGen) ([]*types.SignedMessage, error) { msgs := make([]*types.SignedMessage, cg.msgsPerBlock) for m := range msgs { msg := types.Message{ diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 634855aa0..39e277c93 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -15,33 +15,50 @@ import ( "golang.org/x/xerrors" ) +var ForksAtHeight = map[uint64]func(context.Context, *StateManager, cid.Cid) (cid.Cid, error){ + build.ForkBlizzardHeight: func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) { + log.Warnw("Executing blizzard fork logic") + nstate, err := fixBlizzardAMTBug(ctx, sm, pstate) + if err != nil { + return cid.Undef, xerrors.Errorf("blizzard bug fix failed: %w", err) + } + return nstate, nil + }, + build.ForkFrigidHeight: func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) { + log.Warnw("Executing frigid fork logic") + nstate, err := fixBlizzardAMTBug(ctx, sm, pstate) + if err != nil { + return cid.Undef, xerrors.Errorf("frigid bug fix failed: %w", err) + } + return nstate, nil + }, + build.ForkBootyBayHeight: func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) { + log.Warnw("Executing booty bay fork logic") + nstate, err := fixBlizzardAMTBug(ctx, sm, pstate) + if err != nil { + return cid.Undef, xerrors.Errorf("booty bay bug fix failed: %w", err) + } + return nstate, nil + }, + build.ForkMissingSnowballs: func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) { + log.Warnw("Adding more snow to the world") + nstate, err := fixTooFewSnowballs(ctx, sm, pstate) + if err != nil { + return cid.Undef, xerrors.Errorf("missing snowballs bug fix failed: %w", err) + } + return nstate, nil + }, +} + func (sm *StateManager) handleStateForks(ctx context.Context, pstate cid.Cid, height, parentH uint64) (_ cid.Cid, err error) { for i := parentH; i < height; i++ { - switch i { - case build.ForkBlizzardHeight: - log.Warnw("Executing blizzard fork logic", "height", i) - pstate, err = fixBlizzardAMTBug(ctx, sm, pstate) + f, ok := ForksAtHeight[i] + if ok { + nstate, err := f(ctx, sm, pstate) if err != nil { - return cid.Undef, xerrors.Errorf("blizzard bug fix failed: %w", err) - } - case build.ForkFrigidHeight: - log.Warnw("Executing frigid fork logic", "height", i) - pstate, err = fixBlizzardAMTBug(ctx, sm, pstate) - if err != nil { - return cid.Undef, xerrors.Errorf("frigid bug fix failed: %w", err) - } - case build.ForkBootyBayHeight: - log.Warnw("Executing booty bay fork logic", "height", i) - pstate, err = fixBlizzardAMTBug(ctx, sm, pstate) - if err != nil { - return cid.Undef, xerrors.Errorf("booty bay bug fix failed: %w", err) - } - case build.ForkMissingSnowballs: - log.Warnw("Adding more snow to the world", "height", i) - pstate, err = fixTooFewSnowballs(ctx, sm, pstate) - if err != nil { - return cid.Undef, xerrors.Errorf("missing snowballs bug fix failed: %w", err) + return cid.Undef, err } + pstate = nstate } } diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go new file mode 100644 index 000000000..77a94e25e --- /dev/null +++ b/chain/stmgr/forks_test.go @@ -0,0 +1,231 @@ +package stmgr_test + +import ( + "context" + "fmt" + "io" + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/stmgr" + . "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + + "github.com/ipfs/go-cid" + hamt "github.com/ipfs/go-hamt-ipld" + blockstore "github.com/ipfs/go-ipfs-blockstore" + logging "github.com/ipfs/go-log" + mh "github.com/multiformats/go-multihash" + cbg "github.com/whyrusleeping/cbor-gen" +) + +func init() { + build.SectorSizes = []uint64{1024} + build.MinimumMinerPower = 1024 +} + +const testForkHeight = 40 + +type testActor struct { +} + +type testActorState struct { + HasUpgraded uint64 +} + +func (tas *testActorState) MarshalCBOR(w io.Writer) error { + return cbg.CborWriteHeader(w, cbg.MajUnsignedInt, tas.HasUpgraded) +} + +func (tas *testActorState) UnmarshalCBOR(r io.Reader) error { + t, v, err := cbg.CborReadHeader(r) + if err != nil { + return err + } + if t != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type in test actor state") + } + tas.HasUpgraded = v + return nil +} + +func (ta *testActor) Exports() []interface{} { + return []interface{}{ + 1: ta.Constructor, + 2: ta.TestMethod, + } +} + +func (ta *testActor) Constructor(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, aerrors.ActorError) { + c, err := vmctx.Storage().Put(&testActorState{11}) + if err != nil { + return nil, err + } + + fmt.Println("NEW ACTOR ADDRESS IS: ", vmctx.Message().To.String()) + + return nil, vmctx.Storage().Commit(actors.EmptyCBOR, c) +} + +func (ta *testActor) TestMethod(act *types.Actor, vmctx types.VMContext, params *struct{}) ([]byte, aerrors.ActorError) { + var st testActorState + if err := vmctx.Storage().Get(vmctx.Storage().GetHead(), &st); err != nil { + return nil, err + } + + if vmctx.BlockHeight() > testForkHeight { + if st.HasUpgraded != 55 { + return nil, aerrors.Fatal("fork updating applied in wrong order") + } + } else { + if st.HasUpgraded != 11 { + return nil, aerrors.Fatal("fork updating happened too early") + } + } + + return nil, nil +} + +func TestForkHeightTriggers(t *testing.T) { + logging.SetAllLoggers(logging.LevelInfo) + + ctx := context.TODO() + + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + sm := NewStateManager(cg.ChainStore()) + + inv := vm.NewInvoker() + + pref := cid.NewPrefixV1(cid.Raw, mh.IDENTITY) + actcid, err := pref.Sum([]byte("testactor")) + if err != nil { + t.Fatal(err) + } + + actors.BuiltInActors[actcid] = true + + // predicting the address here... may break if other assumptions change + taddr, err := address.NewIDAddress(1000) + if err != nil { + t.Fatal(err) + } + + stmgr.ForksAtHeight[testForkHeight] = func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) { + cst := hamt.CSTFromBstore(sm.ChainStore().Blockstore()) + st, err := state.LoadStateTree(cst, pstate) + if err != nil { + return cid.Undef, err + } + + act, err := st.GetActor(taddr) + if err != nil { + return cid.Undef, err + } + + var tas testActorState + if err := cst.Get(ctx, act.Head, &tas); err != nil { + return cid.Undef, err + } + + tas.HasUpgraded = 55 + + ns, err := cst.Put(ctx, &tas) + if err != nil { + return cid.Undef, err + } + + act.Head = ns + + if err := st.SetActor(taddr, act); err != nil { + return cid.Undef, err + } + + return st.Flush(ctx) + } + + inv.Register(actcid, &testActor{}, &testActorState{}) + sm.SetVMConstructor(func(c cid.Cid, h uint64, r vm.Rand, a address.Address, b blockstore.Blockstore, s *types.VMSyscalls) (*vm.VM, error) { + nvm, err := vm.NewVM(c, h, r, a, b, s) + if err != nil { + return nil, err + } + nvm.SetInvoker(inv) + return nvm, nil + }) + + cg.SetStateManager(sm) + + var msgs []*types.SignedMessage + + enc, err := actors.SerializeParams(&actors.ExecParams{Code: actcid}) + if err != nil { + t.Fatal(err) + } + + m := &types.Message{ + From: cg.Banker(), + To: actors.InitAddress, + Method: actors.IAMethods.Exec, + Params: enc, + GasLimit: types.NewInt(10000), + GasPrice: types.NewInt(0), + } + sig, err := cg.Wallet().Sign(ctx, cg.Banker(), m.Cid().Bytes()) + if err != nil { + t.Fatal(err) + } + msgs = append(msgs, &types.SignedMessage{ + Signature: *sig, + Message: *m, + }) + + nonce := uint64(1) + cg.GetMessages = func(cg *gen.ChainGen) ([]*types.SignedMessage, error) { + if len(msgs) > 0 { + fmt.Println("added construct method") + m := msgs + msgs = nil + return m, nil + } + + m := &types.Message{ + From: cg.Banker(), + To: taddr, + Method: 2, + Params: nil, + Nonce: nonce, + GasLimit: types.NewInt(10000), + GasPrice: types.NewInt(0), + } + nonce++ + + sig, err := cg.Wallet().Sign(ctx, cg.Banker(), m.Cid().Bytes()) + if err != nil { + return nil, err + } + + return []*types.SignedMessage{ + &types.SignedMessage{ + Signature: *sig, + Message: *m, + }, + }, nil + } + + for i := 0; i < 50; i++ { + _, err = cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + } +} diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 2ccb9aecc..6d018a4d8 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -18,6 +18,7 @@ import ( bls "github.com/filecoin-project/filecoin-ffi" "github.com/ipfs/go-cid" hamt "github.com/ipfs/go-hamt-ipld" + blockstore "github.com/ipfs/go-ipfs-blockstore" logging "github.com/ipfs/go-log/v2" "go.opencensus.io/trace" ) @@ -30,10 +31,12 @@ type StateManager struct { stCache map[string][]cid.Cid compWait map[string]chan struct{} stlk sync.Mutex + newVM func(cid.Cid, uint64, vm.Rand, address.Address, blockstore.Blockstore, *types.VMSyscalls) (*vm.VM, error) } func NewStateManager(cs *store.ChainStore) *StateManager { return &StateManager{ + newVM: vm.NewVM, cs: cs, stCache: make(map[string][]cid.Cid), compWait: make(map[string]chan struct{}), @@ -139,7 +142,7 @@ func (sm *StateManager) computeTipSetState(ctx context.Context, blks []*types.Bl r := store.NewChainRand(sm.cs, cids, blks[0].Height) - vmi, err := vm.NewVM(pstate, blks[0].Height, r, address.Undef, sm.cs.Blockstore(), sm.cs.VMSys()) + vmi, err := sm.newVM(pstate, blks[0].Height, r, address.Undef, sm.cs.Blockstore(), sm.cs.VMSys()) if err != nil { return cid.Undef, cid.Undef, xerrors.Errorf("instantiating VM failed: %w", err) } @@ -636,3 +639,7 @@ func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) err return nil } + +func (sm *StateManager) SetVMConstructor(nvm func(cid.Cid, uint64, vm.Rand, address.Address, blockstore.Blockstore, *types.VMSyscalls) (*vm.VM, error)) { + sm.newVM = nvm +} diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index ab2e54e0f..529329a13 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -23,21 +23,21 @@ type invoker struct { type invokeFunc func(act *types.Actor, vmctx types.VMContext, params []byte) ([]byte, aerrors.ActorError) type nativeCode []invokeFunc -func newInvoker() *invoker { +func NewInvoker() *invoker { inv := &invoker{ builtInCode: make(map[cid.Cid]nativeCode), builtInState: make(map[cid.Cid]reflect.Type), } // add builtInCode using: register(cid, singleton) - inv.register(actors.InitCodeCid, actors.InitActor{}, actors.InitActorState{}) - inv.register(actors.CronCodeCid, actors.CronActor{}, actors.CronActorState{}) - inv.register(actors.StoragePowerCodeCid, actors.StoragePowerActor{}, actors.StoragePowerState{}) - inv.register(actors.StorageMarketCodeCid, actors.StorageMarketActor{}, actors.StorageMarketState{}) - inv.register(actors.StorageMinerCodeCid, actors.StorageMinerActor{}, actors.StorageMinerActorState{}) - inv.register(actors.StorageMiner2CodeCid, actors.StorageMinerActor2{}, actors.StorageMinerActorState{}) - inv.register(actors.MultisigCodeCid, actors.MultiSigActor{}, actors.MultiSigActorState{}) - inv.register(actors.PaymentChannelCodeCid, actors.PaymentChannelActor{}, actors.PaymentChannelActorState{}) + inv.Register(actors.InitCodeCid, actors.InitActor{}, actors.InitActorState{}) + inv.Register(actors.CronCodeCid, actors.CronActor{}, actors.CronActorState{}) + inv.Register(actors.StoragePowerCodeCid, actors.StoragePowerActor{}, actors.StoragePowerState{}) + inv.Register(actors.StorageMarketCodeCid, actors.StorageMarketActor{}, actors.StorageMarketState{}) + inv.Register(actors.StorageMinerCodeCid, actors.StorageMinerActor{}, actors.StorageMinerActorState{}) + inv.Register(actors.StorageMiner2CodeCid, actors.StorageMinerActor2{}, actors.StorageMinerActorState{}) + inv.Register(actors.MultisigCodeCid, actors.MultiSigActor{}, actors.MultiSigActorState{}) + inv.Register(actors.PaymentChannelCodeCid, actors.PaymentChannelActor{}, actors.PaymentChannelActorState{}) return inv } @@ -60,7 +60,7 @@ func (inv *invoker) Invoke(act *types.Actor, vmctx types.VMContext, method uint6 } -func (inv *invoker) register(c cid.Cid, instance Invokee, state interface{}) { +func (inv *invoker) Register(c cid.Cid, instance Invokee, state interface{}) { code, err := inv.transform(instance) if err != nil { panic(err) @@ -165,7 +165,7 @@ func DumpActorState(code cid.Cid, b []byte) (interface{}, error) { return nil, nil } - i := newInvoker() // TODO: register builtins in init block + i := NewInvoker() // TODO: register builtins in init block typ, ok := i.builtInState[code] if !ok { diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 176355689..cfb9c02e2 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -327,7 +327,7 @@ func NewVM(base cid.Cid, height uint64, r Rand, maddr address.Address, cbs block buf: buf, blockHeight: height, blockMiner: maddr, - inv: newInvoker(), + inv: NewInvoker(), rand: r, // TODO: Probably should be a syscall Syscalls: syscalls, }, nil @@ -671,6 +671,10 @@ func Transfer(from, to *types.Actor, amt types.BigInt) error { return nil } +func (vm *VM) SetInvoker(i *invoker) { + vm.inv = i +} + func deductFunds(act *types.Actor, amt types.BigInt) error { if act.Balance.LessThan(amt) { return fmt.Errorf("not enough funds")