feedback + workaround for the prefunded contrat issue

Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
This commit is contained in:
Guillaume Ballet 2024-11-01 15:36:30 +01:00
parent 486da08c8f
commit 0063f267c9
3 changed files with 160 additions and 20 deletions

View file

@ -1062,7 +1062,8 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno
deletes[addrHash] = op
// Short circuit if the origin storage was empty.
if prev.Root == types.EmptyRootHash {
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() {
continue
}
// Remove storage slots belonging to the account.

View file

@ -19,6 +19,7 @@ package core
import (
"bytes"
"encoding/binary"
"encoding/hex"
"math/big"
"slices"
"testing"
@ -162,10 +163,20 @@ func TestProcessVerkle(t *testing.T) {
// Add two contract creations in block #2
if i == 1 {
tx, _ = types.SignTx(types.NewContractCreation(6, big.NewInt(16), 3000000, big.NewInt(875000000), code), signer, testKey)
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 6,
Value: big.NewInt(16),
Gas: 3000000,
GasPrice: big.NewInt(875000000),
Data: code,
}), signer, testKey)
gen.AddTx(tx)
tx, _ = types.SignTx(types.NewContractCreation(7, big.NewInt(0), 3000000, big.NewInt(875000000), codeWithExtCodeCopy), signer, testKey)
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 7,
Value: big.NewInt(0),
Gas: 3000000,
GasPrice: big.NewInt(875000000),
Data: codeWithExtCodeCopy,
}), signer, testKey)
gen.AddTx(tx)
}
})
@ -524,11 +535,20 @@ func TestProcessVerkleExtCodeHashOpcode(t *testing.T) {
if i == 0 {
// Create dummy contract.
tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 100_000, big.NewInt(875000000), dummyContract), signer, testKey)
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(0),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: dummyContract,
}), signer, testKey)
gen.AddTx(tx)
// Create contract with EXTCODEHASH opcode.
tx, _ = types.SignTx(types.NewContractCreation(1, big.NewInt(0), 100_000, big.NewInt(875000000), extCodeHashContract), signer, testKey)
tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 1,
Value: big.NewInt(0),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: extCodeHashContract}), signer, testKey)
gen.AddTx(tx)
} else {
tx, _ := types.SignTx(types.NewTransaction(2, extCodeHashContractAddr, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey)
@ -590,7 +610,11 @@ func TestProcessVerkleBalanceOpcode(t *testing.T) {
common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d").Bytes(),
[]byte{byte(vm.BALANCE)})
tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 100_000, big.NewInt(875000000), txData), signer, testKey)
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(0),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: txData}), signer, testKey)
gen.AddTx(tx)
})
@ -665,7 +689,12 @@ func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) {
if i == 0 {
// Create selfdestruct contract, sending 42 wei.
tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(42), 100_000, big.NewInt(875000000), selfDestructContract), signer, testKey)
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(42),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: selfDestructContract,
}), signer, testKey)
gen.AddTx(tx)
} else {
// Call it.
@ -765,7 +794,12 @@ func TestProcessVerkleSelfDestructInSameTx(t *testing.T) {
_, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) {
gen.SetPoS()
tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(42), 100_000, big.NewInt(875000000), selfDestructContract), signer, testKey)
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(42),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: selfDestructContract,
}), signer, testKey)
gen.AddTx(tx)
})
@ -865,7 +899,12 @@ func TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary(t *testing.T)
gen.SetPoS()
if i == 0 {
// Create self-destruct contract, sending 42 wei.
tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(42), 100_000, big.NewInt(875000000), selfDestructContract), signer, testKey)
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(42),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: selfDestructContract,
}), signer, testKey)
gen.AddTx(tx)
} else {
// Call it.
@ -928,23 +967,22 @@ func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary(t *testing.T) {
deployer = crypto.PubkeyToAddress(testKey.PublicKey)
contract = crypto.CreateAddress(deployer, 0)
)
// Also add the contract-to-destroy
gspec.Alloc[contract] = types.Account{
Balance: big.NewInt(100),
}
// The goal of this test is to test SELFDESTRUCT that happens in a contract
// execution which is created in **the same** transaction sending the remaining
// balance to itself.
t.Logf("Contract: %v", contract.String())
// TODO: investigate why this test succeeds regardless of whether the
// selfdestruct-to-self is used, or selfdestruct-to-zero is used.
selfDestructContract := []byte{byte(vm.ADDRESS), byte(vm.SELFDESTRUCT)}
//selfDestructContract := []byte{byte(vm.NUMBER), byte(vm.SELFDESTRUCT)}
_, _, _, _, stateDiffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) {
gen.SetPoS()
tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(42), 100_000, big.NewInt(875000000), selfDestructContract), signer, testKey)
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(42),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: selfDestructContract,
}), signer, testKey)
gen.AddTx(tx)
})
stateDiff := stateDiffs[0] // state difference of block 1
@ -976,3 +1014,75 @@ func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary(t *testing.T) {
}
}
}
// TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount checks the
// content of the witness if a selfdestruct occurs in the same tx as the one that created it,
// it, but the beneficiary is the selfdestructed account. The difference with the test above,
// is that the created account is prefunded and so the final value should be 0.
func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount(t *testing.T) {
// The test txs were taken from a secondary testnet with chain id 69421
config := *testKaustinenLikeChainConfig
config.ChainID.SetUint64(69421)
var (
signer = types.LatestSigner(&config)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
gspec = verkleTestGenesis(&config)
deployer = crypto.PubkeyToAddress(testKey.PublicKey)
contract = crypto.CreateAddress(deployer, 0)
)
// Prefund the account
gspec.Alloc[contract] = types.Account{
Balance: big.NewInt(100),
}
// The goal of this test is to test SELFDESTRUCT that happens in a contract
// execution which is created in **the same** transaction sending the remaining
// balance to itself.
t.Logf("Contract: %v", contract.String())
selfDestructContract := []byte{byte(vm.ADDRESS), byte(vm.SELFDESTRUCT)}
_, _, _, _, stateDiffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) {
gen.SetPoS()
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: 0,
Value: big.NewInt(42),
Gas: 100_000,
GasPrice: big.NewInt(875000000),
Data: selfDestructContract,
}), signer, testKey)
gen.AddTx(tx)
})
stateDiff := stateDiffs[0] // state difference of block 1
{ // Check self-destructed contract in the witness
selfDestructContractTreeKey := utils.CodeHashKey(contract[:])
var stateDiffIdx = -1
for i, stemStateDiff := range stateDiff {
if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) {
stateDiffIdx = i
break
}
}
if stateDiffIdx == -1 {
t.Fatal("no state diff found for stem")
}
balanceStateDiff := stateDiff[stateDiffIdx].SuffixDiffs[0]
if balanceStateDiff.Suffix != utils.BasicDataLeafKey {
t.Fatal("balance invalid suffix")
}
expected, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000064")
if balanceStateDiff.CurrentValue == nil || !bytes.Equal(balanceStateDiff.CurrentValue[:], expected) {
t.Fatalf("incorrect prestate balance: %x != %x", *balanceStateDiff.CurrentValue, expected)
}
// Ensure that the value is burnt, and therefore that the balance of the self-destructed
// contract isn't modified (it should remain missing from the state)
expected = make([]byte, 32)
if balanceStateDiff.NewValue == nil {
t.Fatal("incorrect nil poststate balance")
}
if !bytes.Equal(balanceStateDiff.NewValue[:], expected[:]) {
t.Fatalf("incorrect poststate balance: %x != %x", *balanceStateDiff.NewValue, expected[:])
}
}
}

View file

@ -17,6 +17,7 @@
package trie
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
@ -169,10 +170,38 @@ func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) er
return t.root.Insert(k, v[:], t.nodeResolver)
}
// DeleteAccount implements state.Trie, deleting the specified account from the
// trie. If the account was not existent in the trie, no error will be returned.
// If the trie is corrupted, an error will be returned.
var zero32 [32]byte
// DeleteAccount leaves the account untouched, as no account deletion can happen
// in verkle.
// There is a special corner case, in which an account that is prefunded, CREATE2-d
// and then SELFDESTRUCT-d should see its funds drained. EIP161 says that account
// should be removed, but this is verboten by the verkle spec. This contains a
// workaround in which the method checks for this corner case, and if so, overwrites
// the balance with 0. This will be removed once the spec has been clarified.
func (t *VerkleTrie) DeleteAccount(addr common.Address) error {
k := utils.BasicDataKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()))
values, err := t.root.(*verkle.InternalNode).GetValuesAtStem(k, t.nodeResolver)
if err != nil {
return fmt.Errorf("Error getting data at %x in delete: %w", k, err)
}
var prefunded bool
for i, v := range values {
switch i {
case 0:
prefunded = len(v) == 32
case 1:
prefunded = len(v) == 32 && bytes.Equal(v, types.EmptyCodeHash[:])
default:
prefunded = v == nil
}
if !prefunded {
break
}
}
if prefunded {
t.root.Insert(k, common.Hash{}.Bytes(), t.nodeResolver)
}
return nil
}