diff --git a/chain/state/statetree.go b/chain/state/statetree.go index b0cabc309..4386efb43 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -26,7 +26,7 @@ type StateTree struct { Store cbor.IpldStore actorcache map[address.Address]*types.Actor - snapshot cid.Cid + snapshots []cid.Cid } func NewStateTree(cst cbor.IpldStore) (*StateTree, error) { @@ -134,7 +134,6 @@ func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) { return cid.Undef, err } } - st.actorcache = make(map[address.Address]*types.Actor) if err := st.root.Flush(ctx); err != nil { return cid.Undef, err @@ -152,10 +151,14 @@ func (st *StateTree) Snapshot(ctx context.Context) error { return err } - st.snapshot = ss + st.snapshots = append(st.snapshots, ss) return nil } +func (st *StateTree) ClearSnapshot() { + st.snapshots = st.snapshots[:len(st.snapshots)-1] +} + func (st *StateTree) RegisterNewAddress(addr address.Address, act *types.Actor) (address.Address, error) { var out address.Address err := st.MutateActor(builtin.InitActorAddr, func(initact *types.Actor) error { @@ -198,10 +201,12 @@ func (a *AdtStore) Context() context.Context { var _ adt.Store = (*AdtStore)(nil) func (st *StateTree) Revert() error { - nd, err := hamt.LoadNode(context.Background(), st.Store, st.snapshot, hamt.UseTreeBitWidth(5)) + revTo := st.snapshots[len(st.snapshots)-1] + nd, err := hamt.LoadNode(context.Background(), st.Store, revTo, hamt.UseTreeBitWidth(5)) if err != nil { return err } + st.actorcache = make(map[address.Address]*types.Actor) st.root = nd return nil diff --git a/chain/state/statetree_test.go b/chain/state/statetree_test.go index 3231e36eb..b22828e19 100644 --- a/chain/state/statetree_test.go +++ b/chain/state/statetree_test.go @@ -2,9 +2,10 @@ package state import ( "context" - "github.com/filecoin-project/specs-actors/actors/builtin" "testing" + "github.com/filecoin-project/specs-actors/actors/builtin" + address "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" cbor "github.com/ipfs/go-ipld-cbor" @@ -144,3 +145,85 @@ func TestSetCache(t *testing.T) { t.Error("nonce didn't match") } } + +func TestSnapshots(t *testing.T) { + ctx := context.Background() + cst := cbor.NewMemCborStore() + st, err := NewStateTree(cst) + if err != nil { + t.Fatal(err) + } + + var addrs []address.Address + //for _, a := range []string{"t15ocrptbu4i5qucjvvwecihd7fqqgzb27pz5l5zy", "t1dpyvgavvl3f4ujlk6odedss54z6rt5gyuknsuva", "t1feiejbkcvozy7iltt2pxzuoq4d2kpbsusugan7a", "t3rgjfqybjx7bahuhfv7nwfg3tlm4i4zyvldfirjvzm5z5xwjoqbj3rfi2mpmlxpqwxxxafgpkjilqzpg7cefa"} { + for _, a := range []string{"t0100", "t0101", "t0102", "t0103"} { + addr, err := address.NewFromString(a) + if err != nil { + t.Fatal(err) + } + addrs = append(addrs, addr) + } + + if err := st.Snapshot(ctx); err != nil { + t.Fatal(err) + } + + if err := st.SetActor(addrs[0], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(55)}); err != nil { + t.Fatal(err) + } + + { // sub call that will fail + if err := st.Snapshot(ctx); err != nil { + t.Fatal(err) + } + + if err := st.SetActor(addrs[1], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(77)}); err != nil { + t.Fatal(err) + } + + if err := st.Revert(); err != nil { + t.Fatal(err) + } + st.ClearSnapshot() + } + + // more operations in top level call... + if err := st.SetActor(addrs[2], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(123)}); err != nil { + t.Fatal(err) + } + + { // sub call that succeeds + if err := st.Snapshot(ctx); err != nil { + t.Fatal(err) + } + + if err := st.SetActor(addrs[3], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(5)}); err != nil { + t.Fatal(err) + } + + st.ClearSnapshot() + } + + if _, err := st.Flush(ctx); err != nil { + t.Fatal(err) + } + + assertHas(t, st, addrs[0]) + assertNotHas(t, st, addrs[1]) + assertHas(t, st, addrs[2]) + assertHas(t, st, addrs[3]) +} + +func assertHas(t *testing.T, st *StateTree, addr address.Address) { + _, err := st.GetActor(addr) + if err != nil { + t.Fatal(err) + } +} + +func assertNotHas(t *testing.T, st *StateTree, addr address.Address) { + _, err := st.GetActor(addr) + if err == nil { + t.Fatal("shouldnt have found actor", addr) + } +} diff --git a/chain/vm/validation_test.go b/chain/vm/validation_test.go index de1fd6381..8532773d3 100644 --- a/chain/vm/validation_test.go +++ b/chain/vm/validation_test.go @@ -8,7 +8,6 @@ import ( suites "github.com/filecoin-project/chain-validation/suites" "github.com/filecoin-project/chain-validation/suites/message" - "github.com/filecoin-project/chain-validation/suites/tipset" factory "github.com/filecoin-project/lotus/chain/validation" ) @@ -35,7 +34,6 @@ var TestSuiteSkipper TestSkipper func init() { // initialize the test skipper with tests being skipped TestSuiteSkipper = TestSkipper{testSkips: []suites.TestCase{ - tipset.TestInternalMessageApplicationFailure, // Fails due to gas mismatches message.TestPaych, diff --git a/chain/vm/vm.go b/chain/vm/vm.go index d9571b351..001f59d4a 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -163,7 +163,18 @@ func (vmc *VMContext) Send(to address.Address, method abi.MethodNum, value types GasLimit: vmc.gasAvailable, } + st := vmc.state + if err := st.Snapshot(ctx); err != nil { + return nil, aerrors.Fatalf("snapshot failed: %s", err) + } + defer st.ClearSnapshot() + ret, err, _ := vmc.vm.send(ctx, msg, vmc, 0) + if err != nil { + if err := st.Revert(); err != nil { + return nil, aerrors.Escalate(err, "failed to revert state tree after failed subcall") + } + } return ret, err } @@ -366,6 +377,7 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *VMContext, gasCharge uint64) ([]byte, aerrors.ActorError, *VMContext) { st := vm.cstate + fromActor, err := st.GetActor(msg.From) if err != nil { return nil, aerrors.Absorb(err, 1, "could not find source actor"), nil @@ -453,6 +465,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, if err := st.Snapshot(ctx); err != nil { return nil, xerrors.Errorf("snapshot failed: %w", err) } + defer st.ClearSnapshot() fromActor, err := st.GetActor(msg.From) if err != nil { diff --git a/go.mod b/go.mod index 0cfdf04d4..8eaa8f203 100644 --- a/go.mod +++ b/go.mod @@ -116,5 +116,3 @@ replace github.com/golangci/golangci-lint => github.com/golangci/golangci-lint v replace github.com/filecoin-project/filecoin-ffi => ./extern/filecoin-ffi replace github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0 - -replace github.com/filecoin-project/specs-actors => ../specs-actors diff --git a/go.sum b/go.sum index a3768a206..fa0e082ec 100644 --- a/go.sum +++ b/go.sum @@ -133,6 +133,8 @@ github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.m github.com/filecoin-project/specs-actors v0.0.0-20200226200336-94c9b92b2775/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf h1:3IojVqJAD5IXMxvZ+WYx+LRbfSB/rOXpYBuHh6o3XkY= github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= +github.com/filecoin-project/specs-actors v0.0.0-20200304210626-21ee86aadcb9 h1:5/XkV9N7Zlidi2RYY/04BToD/XeQrudUseI7Gx6owl8= +github.com/filecoin-project/specs-actors v0.0.0-20200304210626-21ee86aadcb9/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 h1:EzDjxMg43q1tA2c0MV3tNbaontnHLplHyFF6M5KiVP0=