Skip to content

Commit a308f01

Browse files
authored
core/state: fix copy-commit-copy (#20113)
* core/state: revert noop finalise, fix copy-commit-copy * core/state: reintroduce net sstore tracking, extend tests for it
1 parent 311419c commit a308f01

File tree

2 files changed

+167
-5
lines changed

2 files changed

+167
-5
lines changed

core/state/statedb.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,13 @@ func (self *StateDB) Copy() *StateDB {
588588
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
589589
// nil
590590
if object, exist := self.stateObjects[addr]; exist {
591+
// Even though the original object is dirty, we are not copying the journal,
592+
// so we need to make sure that anyside effect the journal would have caused
593+
// during a commit (or similar op) is already applied to the copy.
591594
state.stateObjects[addr] = object.deepCopy(state)
592-
state.stateObjectsDirty[addr] = struct{}{}
595+
596+
state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits
597+
state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
593598
}
594599
}
595600
// Above, we don't copy the actual journal. This means that if the copy is copied, the

core/state/statedb_test.go

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,18 +438,175 @@ func (s *StateSuite) TestTouchDelete(c *check.C) {
438438
// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy.
439439
// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512
440440
func TestCopyOfCopy(t *testing.T) {
441-
sdb, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
441+
state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
442442
addr := common.HexToAddress("aaaa")
443-
sdb.SetBalance(addr, big.NewInt(42))
443+
state.SetBalance(addr, big.NewInt(42))
444444

445-
if got := sdb.Copy().GetBalance(addr).Uint64(); got != 42 {
445+
if got := state.Copy().GetBalance(addr).Uint64(); got != 42 {
446446
t.Fatalf("1st copy fail, expected 42, got %v", got)
447447
}
448-
if got := sdb.Copy().Copy().GetBalance(addr).Uint64(); got != 42 {
448+
if got := state.Copy().Copy().GetBalance(addr).Uint64(); got != 42 {
449449
t.Fatalf("2nd copy fail, expected 42, got %v", got)
450450
}
451451
}
452452

453+
// Tests a regression where committing a copy lost some internal meta information,
454+
// leading to corrupted subsequent copies.
455+
//
456+
// See https://github.com/ethereum/go-ethereum/issues/20106.
457+
func TestCopyCommitCopy(t *testing.T) {
458+
state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
459+
460+
// Create an account and check if the retrieved balance is correct
461+
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
462+
skey := common.HexToHash("aaa")
463+
sval := common.HexToHash("bbb")
464+
465+
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
466+
state.SetCode(addr, []byte("hello")) // Change an external metadata
467+
state.SetState(addr, skey, sval) // Change the storage trie
468+
469+
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
470+
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
471+
}
472+
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
473+
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
474+
}
475+
if val := state.GetState(addr, skey); val != sval {
476+
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
477+
}
478+
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
479+
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
480+
}
481+
// Copy the non-committed state database and check pre/post commit balance
482+
copyOne := state.Copy()
483+
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
484+
t.Fatalf("first copy pre-commit balance mismatch: have %v, want %v", balance, 42)
485+
}
486+
if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
487+
t.Fatalf("first copy pre-commit code mismatch: have %x, want %x", code, []byte("hello"))
488+
}
489+
if val := copyOne.GetState(addr, skey); val != sval {
490+
t.Fatalf("first copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
491+
}
492+
if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) {
493+
t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
494+
}
495+
496+
copyOne.Commit(false)
497+
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
498+
t.Fatalf("first copy post-commit balance mismatch: have %v, want %v", balance, 42)
499+
}
500+
if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
501+
t.Fatalf("first copy post-commit code mismatch: have %x, want %x", code, []byte("hello"))
502+
}
503+
if val := copyOne.GetState(addr, skey); val != sval {
504+
t.Fatalf("first copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
505+
}
506+
if val := copyOne.GetCommittedState(addr, skey); val != sval {
507+
t.Fatalf("first copy post-commit committed storage slot mismatch: have %x, want %x", val, sval)
508+
}
509+
// Copy the copy and check the balance once more
510+
copyTwo := copyOne.Copy()
511+
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
512+
t.Fatalf("second copy balance mismatch: have %v, want %v", balance, 42)
513+
}
514+
if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
515+
t.Fatalf("second copy code mismatch: have %x, want %x", code, []byte("hello"))
516+
}
517+
if val := copyTwo.GetState(addr, skey); val != sval {
518+
t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval)
519+
}
520+
if val := copyTwo.GetCommittedState(addr, skey); val != sval {
521+
t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval)
522+
}
523+
}
524+
525+
// Tests a regression where committing a copy lost some internal meta information,
526+
// leading to corrupted subsequent copies.
527+
//
528+
// See https://github.com/ethereum/go-ethereum/issues/20106.
529+
func TestCopyCopyCommitCopy(t *testing.T) {
530+
state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()))
531+
532+
// Create an account and check if the retrieved balance is correct
533+
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
534+
skey := common.HexToHash("aaa")
535+
sval := common.HexToHash("bbb")
536+
537+
state.SetBalance(addr, big.NewInt(42)) // Change the account trie
538+
state.SetCode(addr, []byte("hello")) // Change an external metadata
539+
state.SetState(addr, skey, sval) // Change the storage trie
540+
541+
if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
542+
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
543+
}
544+
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
545+
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
546+
}
547+
if val := state.GetState(addr, skey); val != sval {
548+
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
549+
}
550+
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
551+
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
552+
}
553+
// Copy the non-committed state database and check pre/post commit balance
554+
copyOne := state.Copy()
555+
if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
556+
t.Fatalf("first copy balance mismatch: have %v, want %v", balance, 42)
557+
}
558+
if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
559+
t.Fatalf("first copy code mismatch: have %x, want %x", code, []byte("hello"))
560+
}
561+
if val := copyOne.GetState(addr, skey); val != sval {
562+
t.Fatalf("first copy non-committed storage slot mismatch: have %x, want %x", val, sval)
563+
}
564+
if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) {
565+
t.Fatalf("first copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
566+
}
567+
// Copy the copy and check the balance once more
568+
copyTwo := copyOne.Copy()
569+
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
570+
t.Fatalf("second copy pre-commit balance mismatch: have %v, want %v", balance, 42)
571+
}
572+
if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
573+
t.Fatalf("second copy pre-commit code mismatch: have %x, want %x", code, []byte("hello"))
574+
}
575+
if val := copyTwo.GetState(addr, skey); val != sval {
576+
t.Fatalf("second copy pre-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
577+
}
578+
if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) {
579+
t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{})
580+
}
581+
copyTwo.Commit(false)
582+
if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
583+
t.Fatalf("second copy post-commit balance mismatch: have %v, want %v", balance, 42)
584+
}
585+
if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
586+
t.Fatalf("second copy post-commit code mismatch: have %x, want %x", code, []byte("hello"))
587+
}
588+
if val := copyTwo.GetState(addr, skey); val != sval {
589+
t.Fatalf("second copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval)
590+
}
591+
if val := copyTwo.GetCommittedState(addr, skey); val != sval {
592+
t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval)
593+
}
594+
// Copy the copy-copy and check the balance once more
595+
copyThree := copyTwo.Copy()
596+
if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 {
597+
t.Fatalf("third copy balance mismatch: have %v, want %v", balance, 42)
598+
}
599+
if code := copyThree.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
600+
t.Fatalf("third copy code mismatch: have %x, want %x", code, []byte("hello"))
601+
}
602+
if val := copyThree.GetState(addr, skey); val != sval {
603+
t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval)
604+
}
605+
if val := copyThree.GetCommittedState(addr, skey); val != sval {
606+
t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval)
607+
}
608+
}
609+
453610
// TestDeleteCreateRevert tests a weird state transition corner case that we hit
454611
// while changing the internals of statedb. The workflow is that a contract is
455612
// self destructed, then in a followup transaction (but same block) it's created

0 commit comments

Comments
 (0)