diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 5e2a21023..b6b497ece 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2376,11 +2376,9 @@ func TestDeleteRecreateSlots(t *testing.T) { engine = ethash.NewFaker() db = rawdb.NewMemoryDatabase() // A sender who makes transactions, has some funds - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(1000000000) - - aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) @@ -2403,7 +2401,7 @@ func TestDeleteRecreateSlots(t *testing.T) { byte(vm.PUSH1), 0x4, // location byte(vm.SSTORE), // Set slot[4] = 1 // Slots are set, now return the code - byte(vm.PUSH2), 0x88, 0xff, // Push code on stack + byte(vm.PUSH2), byte(vm.PC), byte(vm.SELFDESTRUCT), // Push code on stack byte(vm.PUSH1), 0x0, // memory start on stack byte(vm.MSTORE), // Code is now in memory. @@ -2428,6 +2426,10 @@ func TestDeleteRecreateSlots(t *testing.T) { byte(vm.CREATE2), }...) + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + gspec := &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{ @@ -2563,3 +2565,196 @@ func TestDeleteRecreateAccount(t *testing.T) { t.Errorf("got %x exp %x", got, exp) } } + +// TestDeleteRecreateSlotsAcrossManyBlocks tests multiple state-transition that contains both deletion +// and recreation of contract state. +// Contract A exists, has slots 1 and 2 set +// Tx 1: Selfdestruct A +// Tx 2: Re-create A, set slots 3 and 4 +// Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, +// and then the new slots exist +func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { + var ( + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. Set slots 3=blocknum+1, 4=4, + // 2. Return aaCode + + initCode := []byte{ + byte(vm.PUSH1), 0x1, // + byte(vm.NUMBER), // value = number + 1 + byte(vm.ADD), // + byte(vm.PUSH1), 0x3, // location + byte(vm.SSTORE), // Set slot[3] = number + 1 + byte(vm.PUSH1), 0x4, // value + byte(vm.PUSH1), 0x4, // location + byte(vm.SSTORE), // Set slot[4] = 4 + // Slots are set, now return the code + byte(vm.PUSH2), byte(vm.PC), byte(vm.SELFDESTRUCT), // Push code on stack + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + // Code is now in memory. + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), byte(32 - 2), // offset + byte(vm.RETURN), + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + // The contract BB recreates AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + genesis := gspec.MustCommit(db) + var nonce uint64 + + type expectation struct { + exist bool + blocknum int + values map[int]int + } + var current = &expectation{ + exist: true, // exists in genesis + blocknum: 0, + values: map[int]int{1: 1, 2: 2}, + } + var expectations []*expectation + var newDestruct = func(e *expectation) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, aa, + big.NewInt(0), 50000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + nonce++ + if e.exist { + e.exist = false + e.values = nil + } + t.Logf("block %d; adding destruct\n", e.blocknum) + return tx + } + var newResurrect = func(e *expectation) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, bb, + big.NewInt(0), 100000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + nonce++ + if !e.exist { + e.exist = true + e.values = map[int]int{3: e.blocknum + 1, 4: 4} + } + t.Logf("block %d; adding resurrect\n", e.blocknum) + return tx + } + + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 150, func(i int, b *BlockGen) { + var exp = new(expectation) + exp.blocknum = i + 1 + exp.values = make(map[int]int) + for k, v := range current.values { + exp.values[k] = v + } + exp.exist = current.exist + + b.SetCoinbase(common.Address{1}) + if i%2 == 0 { + b.AddTx(newDestruct(exp)) + } + if i%3 == 0 { + b.AddTx(newResurrect(exp)) + } + if i%5 == 0 { + b.AddTx(newDestruct(exp)) + } + if i%7 == 0 { + b.AddTx(newResurrect(exp)) + } + expectations = append(expectations, exp) + current = exp + }) + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + //Debug: true, + //Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + var asHash = func(num int) common.Hash { + return common.BytesToHash([]byte{byte(num)}) + } + for i, block := range blocks { + blockNum := i + 1 + if n, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + // If all is correct, then slot 1 and 2 are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("block %d, got %x exp %x", blockNum, got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("block %d, got %x exp %x", blockNum, got, exp) + } + exp := expectations[i] + if exp.exist { + if !statedb.Exist(aa) { + t.Fatalf("block %d, expected %v to exist, it did not", blockNum, aa) + } + for slot, val := range exp.values { + if gotValue, expValue := statedb.GetState(aa, asHash(slot)), asHash(val); gotValue != expValue { + t.Fatalf("block %d, slot %d, got %x exp %x", blockNum, slot, gotValue, expValue) + } + } + } else { + if statedb.Exist(aa) { + t.Fatalf("block %d, expected %v to not exist, it did", blockNum, aa) + } + } + } +}