diff --git a/core/state/statedb.go b/core/state/statedb.go index 08f18c2d2f..97c6e4a017 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -474,6 +474,16 @@ func (self *StateDB) Copy() *StateDB { state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state) state.stateObjectsDirty[addr] = struct{}{} } + // Above, we don't copy the actual journal. This means that if the copy is copied, the + // loop above will be a no-op, since the copy's journal is empty. + // Thus, here we iterate over stateObjects, to enable copies of copies + for addr := range self.stateObjectsDirty { + if _, exist := state.stateObjects[addr]; !exist { + state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state) + state.stateObjectsDirty[addr] = struct{}{} + } + } + for hash, logs := range self.logs { state.logs[hash] = make([]*types.Log, len(logs)) copy(state.logs[hash], logs) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 340c840f13..420ca745cc 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -422,3 +422,19 @@ func (s *StateSuite) TestTouchDelete(c *check.C) { c.Fatal("expected no dirty state object") } } + +// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. +// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 +func TestCopyOfCopy(t *testing.T) { + db, _ := ethdb.NewMemDatabase() + sdb, _ := New(common.Hash{}, NewDatabase(db)) + addr := common.HexToAddress("aaaa") + sdb.SetBalance(addr, big.NewInt(42)) + + if got := sdb.Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("1st copy fail, expected 42, got %v", got) + } + if got := sdb.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("2nd copy fail, expected 42, got %v", got) + } +}