Skip to content

Commit

Permalink
test stateless block execution (#18)
Browse files Browse the repository at this point in the history
* test stateless block execution

* Force tree resolution before generating the proof
  • Loading branch information
gballet authored Sep 10, 2021
1 parent a7656e3 commit fcb4eb1
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 35 deletions.
89 changes: 88 additions & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)

// BlockGen creates blocks for testing.
Expand All @@ -43,6 +44,7 @@ type BlockGen struct {
txs []*types.Transaction
receipts []*types.Receipt
uncles []*types.Header
witness *types.AccessWitness

config *params.ChainConfig
engine consensus.Engine
Expand Down Expand Up @@ -103,10 +105,11 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
b.SetCoinbase(common.Address{})
}
b.statedb.Prepare(tx.Hash(), len(b.txs))
receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
receipt, accesses, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
if err != nil {
panic(err)
}
b.witness.Merge(accesses)
b.txs = append(b.txs, tx)
b.receipts = append(b.receipts, receipt)
}
Expand Down Expand Up @@ -250,6 +253,90 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
return blocks, receipts
}

func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
if config == nil {
config = params.TestChainConfig
}
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
chainreader := &fakeChainReader{config: config}
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine, witness: types.NewAccessWitness()}
b.header = makeHeader(chainreader, parent, statedb, b.engine)

// Mutate the state and block according to any hard-fork specs
if daoBlock := config.DAOForkBlock; daoBlock != nil {
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 {
if config.DAOForkSupport {
b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
}
}
}
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
misc.ApplyDAOHardFork(statedb)
}
// Execute any user modifications to the block
if gen != nil {
gen(i, b)
}
if b.engine != nil {
// Finalize and seal the block
block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
if err != nil {
panic(err)
}

// Write state changes to db
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
if err != nil {
panic(fmt.Sprintf("state write error: %v", err))
}
if err := statedb.Database().TrieDB().Commit(root, false, nil); err != nil {
panic(fmt.Sprintf("trie write error: %v", err))
}

// Generate an associated verkle proof
if tr := statedb.GetTrie(); tr.IsVerkle() {
vtr := tr.(*trie.VerkleTrie)
// Generate the proof if we are using a verkle tree
// WORKAROUND: make sure all keys are resolved
// before building the proof. Ultimately, node
// resolution can be done with a prefetcher or
// from GetCommitmentsAlongPath.
keys := b.witness.Keys()
for _, key := range keys {
out, err := vtr.TryGet(key)
if err != nil {
panic(err)
}
if len(out) == 0 {
panic(fmt.Sprintf("%x should be present in the tree", key))
}
}
vtr.Hash()
p, err := vtr.ProveAndSerialize(keys)
b.header.VerkleProof = p
if err != nil {
panic(err)
}
}
return block, b.receipts
}
return nil, nil
}
for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabaseWithConfig(db, &trie.Config{UseVerkle: true}), nil)
if err != nil {
panic(err)
}
block, receipt := genblock(i, parent, statedb)
blocks[i] = block
receipts[i] = receipt
parent = block
}
return blocks, receipts
}

func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
var time uint64
if parent.Time() == 0 {
Expand Down
12 changes: 12 additions & 0 deletions core/state/snapshot/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"sync"
"sync/atomic"

"github.com/VictoriaMetrics/fastcache"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/ethdb"
Expand Down Expand Up @@ -193,6 +194,17 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm
}
if err != nil {
if rebuild {
if useVerkle {
snap.layers = map[common.Hash]snapshot{
root: &diskLayer{
diskdb: diskdb,
triedb: triedb,
root: root,
cache: fastcache.New(cache * 1024 * 1024),
},
}
return snap, nil
}
log.Warn("Failed to load snapshot, regenerating", "err", err)
snap.Rebuild(root)
return snap, nil
Expand Down
19 changes: 7 additions & 12 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,18 +324,13 @@ func TestProcessStateless(t *testing.T) {
genesis := gspec.MustCommit(db, nil)
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
defer blockchain.Stop()
var makeTx = func(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *types.Transaction {
tx, _ := types.SignTx(types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data), signer, testKey)
return tx
}
bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))
tooBigNumber := new(big.Int).Set(bigNumber)
tooBigNumber.Add(tooBigNumber, common.Big1)
txs := []*types.Transaction{
makeTx(0, common.Address{}, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil),
}
block := GenerateBadBlock(genesis, ethash.NewFaker(), txs, gspec.Config)
_, err := blockchain.InsertChain(types.Blocks{block})
chain, _ := GenerateVerkleChain(gspec.Config, genesis, ethash.NewFaker(), db, 1, func(_ int, gen *BlockGen) {
toaddr := common.Address{}
tx, _ := types.SignTx(types.NewTransaction(0, toaddr, big.NewInt(0), params.TxGas, big.NewInt(875000000), nil), signer, testKey)
gen.AddTx(tx)

})
_, err := blockchain.InsertChain(chain)
if err != nil {
t.Fatalf("block imported with error: %v", err)
}
Expand Down
11 changes: 10 additions & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
)

/*
Expand Down Expand Up @@ -112,7 +113,7 @@ func (result *ExecutionResult) Revert() []byte {
}

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
Expand Down Expand Up @@ -289,6 +290,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
}
if st.evm.TxContext.Accesses != nil {
toBalance := trieUtils.GetTreeKeyBalance(*msg.To())
fromBalance := trieUtils.GetTreeKeyBalance(msg.From())
fromNonce := trieUtils.GetTreeKeyNonce(msg.From())
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(toBalance)
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce)
gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromBalance)
}
st.gas -= gas

// Check clause 6
Expand Down
38 changes: 38 additions & 0 deletions core/types/access_witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package types

import "github.com/ethereum/go-ethereum/common"
import "github.com/ethereum/go-ethereum/params"

type AccessWitness struct {
Witness map[common.Hash]map[byte]struct{}
Expand All @@ -26,6 +27,43 @@ func NewAccessWitness() *AccessWitness {
return &AccessWitness{Witness: make(map[common.Hash]map[byte]struct{})}
}

// TouchAddress adds any missing addr to the witness and returns respectively
// true if the stem or the stub weren't arleady present.
func (aw *AccessWitness) TouchAddress(addr []byte) (bool, bool) {
var (
stem common.Hash
newStem bool
newSelector bool
selector = addr[31]
)
copy(stem[:], addr[:31])

// Check for the presence of the stem
if _, newStem := aw.Witness[stem]; !newStem {
aw.Witness[stem] = make(map[byte]struct{})
}

// Check for the presence of the selector
if _, newSelector := aw.Witness[stem][selector]; !newSelector {
aw.Witness[stem][selector] = struct{}{}
}

return newStem, newSelector
}

func (aw *AccessWitness) TouchAddressAndChargeGas(addr []byte) uint64 {
var gas uint64

nstem, nsel := aw.TouchAddress(addr)
if nstem {
gas += params.WitnessBranchCost
}
if nsel {
gas += params.WitnessChunkCost
}
return gas
}

func (aw *AccessWitness) Merge(other *AccessWitness) {
for k, mo := range other.Witness {
if ma, ok := aw.Witness[k]; ok {
Expand Down
23 changes: 10 additions & 13 deletions core/vm/gas_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
_, ok := evm.TxContext.Accesses.Witness[subtree]
if !ok {
evm.TxContext.Accesses.Witness[subtree] = make(map[byte]struct{})
usedGas += gasWitnessBranchCost
usedGas += params.WitnessBranchCost
}

_, ok = evm.TxContext.Accesses.Witness[subtree][subleaf]
if !ok {
usedGas += gasWitnessChunkCost
usedGas += params.WitnessChunkCost
evm.TxContext.Accesses.Witness[subtree][subleaf] = struct{}{}
}

Expand All @@ -114,9 +114,6 @@ var (
gasCodeCopyStateful = memoryCopierGas(2)
gasExtCodeCopyStateful = memoryCopierGas(3)
gasReturnDataCopy = memoryCopierGas(2)

gasWitnessBranchCost = uint64(1900)
gasWitnessChunkCost = uint64(200)
)

func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
Expand Down Expand Up @@ -147,12 +144,12 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
_, ok := evm.TxContext.Accesses.Witness[subtree]
if !ok {
evm.TxContext.Accesses.Witness[subtree] = make(map[byte]struct{})
statelessGas += gasWitnessBranchCost
statelessGas += params.WitnessBranchCost
}

_, ok = evm.TxContext.Accesses.Witness[subtree][subleaf]
if !ok {
statelessGas += gasWitnessChunkCost
statelessGas += params.WitnessChunkCost
evm.TxContext.Accesses.Witness[subtree][subleaf] = struct{}{}
}

Expand Down Expand Up @@ -192,12 +189,12 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
_, ok := evm.TxContext.Accesses.Witness[subtree]
if !ok {
evm.TxContext.Accesses.Witness[subtree] = make(map[byte]struct{})
statelessGas += gasWitnessBranchCost
statelessGas += params.WitnessBranchCost
}

_, ok = evm.TxContext.Accesses.Witness[subtree][subleaf]
if !ok {
statelessGas += gasWitnessChunkCost
statelessGas += params.WitnessChunkCost
evm.TxContext.Accesses.Witness[subtree][subleaf] = struct{}{}
}

Expand All @@ -218,12 +215,12 @@ func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySiz
_, ok := evm.TxContext.Accesses.Witness[subtree]
if !ok {
evm.TxContext.Accesses.Witness[subtree] = make(map[byte]struct{})
usedGas += gasWitnessBranchCost
usedGas += params.WitnessBranchCost
}

_, ok = evm.TxContext.Accesses.Witness[subtree][subleaf]
if !ok {
usedGas += gasWitnessChunkCost
usedGas += params.WitnessChunkCost
evm.TxContext.Accesses.Witness[subtree][subleaf] = struct{}{}
}

Expand Down Expand Up @@ -480,12 +477,12 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
_, ok := evm.TxContext.Accesses.Witness[subtree]
if !ok {
evm.TxContext.Accesses.Witness[subtree] = make(map[byte]struct{})
gas += gasWitnessBranchCost
gas += params.WitnessBranchCost
}

_, ok = evm.TxContext.Accesses.Witness[subtree][subleaf]
if !ok {
gas += gasWitnessChunkCost
gas += params.WitnessChunkCost
evm.TxContext.Accesses.Witness[subtree][subleaf] = struct{}{}
}
}
Expand Down
5 changes: 3 additions & 2 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
Expand Down Expand Up @@ -204,11 +205,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
subleaf := index[31]
if _, ok := in.evm.TxContext.Accesses.Witness[subtree]; !ok {
in.evm.TxContext.Accesses.Witness[subtree] = make(map[byte]struct{})
contract.Gas -= gasWitnessBranchCost
contract.Gas -= params.WitnessBranchCost
}
if _, ok := in.evm.TxContext.Accesses.Witness[subtree][subleaf]; !ok {
in.evm.TxContext.Accesses.Witness[subtree][subleaf] = struct{}{}
contract.Gas -= gasWitnessChunkCost
contract.Gas -= params.WitnessChunkCost
}

if in.evm.accesses != nil {
Expand Down
4 changes: 4 additions & 0 deletions params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ const (
// up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529
RefundQuotient uint64 = 2
RefundQuotientEIP3529 uint64 = 5

// Verkle tree EIP: costs associated to witness accesses
WitnessBranchCost = uint64(1900)
WitnessChunkCost = uint64(200)
)

// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
Expand Down
Loading

0 comments on commit fcb4eb1

Please sign in to comment.