Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verkle without conversion #10

Open
wants to merge 5 commits into
base: pbss-verkle
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/geth/verkle.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error
switch node := root.(type) {
case *verkle.InternalNode:
for i, child := range node.Children() {
childC := child.Commit().Bytes()
childC := child.Commitment().Bytes()

childS, err := resolver(childC[:])
if bytes.Equal(childC[:], zero[:]) {
Expand All @@ -84,7 +84,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error
return fmt.Errorf("could not find child %x in db: %w", childC, err)
}
// depth is set to 0, the tree isn't rebuilt so it's not a problem
childN, err := verkle.ParseNode(childS, 0, childC[:])
childN, err := verkle.ParseNode(childS, 0)
if err != nil {
return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err)
}
Expand Down Expand Up @@ -144,7 +144,7 @@ func verifyVerkle(ctx *cli.Context) error {
if err != nil {
return err
}
root, err := verkle.ParseNode(serializedRoot, 0, rootC[:])
root, err := verkle.ParseNode(serializedRoot, 0)
if err != nil {
return err
}
Expand Down Expand Up @@ -193,7 +193,7 @@ func expandVerkle(ctx *cli.Context) error {
if err != nil {
return err
}
root, err := verkle.ParseNode(serializedRoot, 0, rootC[:])
root, err := verkle.ParseNode(serializedRoot, 0)
if err != nil {
return err
}
Expand All @@ -203,7 +203,7 @@ func expandVerkle(ctx *cli.Context) error {
root.Get(key, chaindb.Get)
}

if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0600); err != nil {
if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0o600); err != nil {
log.Error("Failed to dump file", "err", err)
} else {
log.Info("Tree was dumped to file", "file", "dump.dot")
Expand Down
3 changes: 3 additions & 0 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
amount := new(big.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, big.NewInt(params.GWei))
state.AddBalance(w.Address, amount)

// The returned gas is not charged
state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:])
}
// No block reward which is issued by consensus layer instead.
}
Expand Down
17 changes: 17 additions & 0 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
"golang.org/x/crypto/sha3"
)

Expand Down Expand Up @@ -564,10 +565,26 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)

// This should not happen, but it's useful for replay tests
if config.IsVerkle(header.Number, header.Time) {
uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes())
state.Witness().TouchAddressOnReadAndComputeGas(uncleCoinbase)
}
state.AddBalance(uncle.Coinbase, r)

r.Div(blockReward, big32)
reward.Add(reward, r)
}
if config.IsVerkle(header.Number, header.Time) {
coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes())
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
coinbase[31] = utils.VersionLeafKey // mark version
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
coinbase[31] = utils.NonceLeafKey // mark nonce
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
coinbase[31] = utils.CodeKeccakLeafKey // mark code keccak
state.Witness().TouchAddressOnReadAndComputeGas(coinbase)
}
state.AddBalance(header.Coinbase, reward)
}
3 changes: 3 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
}
// Make sure the state associated with the block is available
head := bc.CurrentBlock()

if !bc.HasState(head.Root) {
// Head state is missing, before the state recovery, find out the
// disk layer point of snapshot(if it's enabled). Make sure the
Expand Down Expand Up @@ -430,6 +431,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
Recovery: recover,
NoBuild: bc.cacheConfig.SnapshotNoBuild,
AsyncBuild: !bc.cacheConfig.SnapshotWait,
Verkle: chainConfig.IsVerkle(head.Number, head.Time),
}
bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root)
}
Expand Down Expand Up @@ -1776,6 +1778,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
if parent == nil {
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
}

statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
if err != nil {
return it.index, err
Expand Down
117 changes: 117 additions & 0 deletions core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/types"
"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"
"github.com/gballet/go-verkle"
)

// BlockGen creates blocks for testing.
Expand Down Expand Up @@ -367,10 +369,125 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
if err != nil {
panic(err)
}
if genesis.Config != nil && genesis.Config.IsVerkle(genesis.ToBlock().Number(), genesis.ToBlock().Time()) {
blocks, receipts, _, _ := GenerateVerkleChain(genesis.Config, genesis.ToBlock(), engine, db, n, gen)
return db, blocks, receipts
}
blocks, receipts := GenerateChain(genesis.Config, genesis.ToBlock(), engine, db, n, gen)
return db, 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, []*verkle.VerkleProof, []verkle.StateDiff) {
if config == nil {
config = params.TestChainConfig
}
proofs := make([]*verkle.VerkleProof, 0, n)
keyvals := make([]verkle.StateDiff, 0, n)
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
chainreader := &fakeChainReader{config: config}
var preStateTrie *trie.VerkleTrie
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}
b.header = makeHeader(chainreader, parent, statedb, b.engine)
preState := statedb.Copy()
fmt.Println("prestate", preState.GetTrie().(*trie.VerkleTrie).ToDot())

// 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, b.withdrawals)
if err != nil {
panic(err)
}

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

// Generate an associated verkle proof
tr := preState.GetTrie()
if !tr.IsVerkle() {
panic("tree should be verkle")
}

vtr := tr.(*trie.VerkleTrie)
// Make sure all keys are resolved before
// building the proof. Ultimately, node
// resolution can be done with a prefetcher
// or from GetCommitmentsAlongPath.
kvs := make(map[string][]byte)
keys := statedb.Witness().Keys()
for _, key := range keys {
v, err := vtr.GetWithHashedKey(key)
if err != nil {
panic(err)
}
kvs[string(key)] = v
}

// Initialize the preStateTrie if it is nil, this should
// correspond to the genesis block. This is a workaround
// needed until the main verkle PR is rebased on top of
// PBSS.
if preStateTrie == nil {
preStateTrie = vtr
}

vtr.Hash()
p, k, err := preStateTrie.ProveAndSerialize(statedb.Witness().Keys(), kvs)
if err != nil {
panic(err)
}
proofs = append(proofs, p)
keyvals = append(keyvals, k)

// save the current state of the trie for producing the proof for the next block,
// since reading it from disk is broken with the intermediate PBSS-like system we
// have: it will read the post-state as this is the only state present on disk.
// This is a workaround needed until the main verkle PR is rebased on top of PBSS.
preStateTrie = statedb.GetTrie().(*trie.VerkleTrie)

return block, b.receipts
}
return nil, nil
}
var snaps *snapshot.Tree
for i := 0; i < n; i++ {
triedb := state.NewDatabaseWithConfig(db, nil)
triedb.EndVerkleTransition()
statedb, err := state.New(parent.Root(), triedb, snaps)
if err != nil {
panic(fmt.Sprintf("could not find state for block %d: err=%v, parent root=%x", i, err, parent.Root()))
}
block, receipt := genblock(i, parent, statedb)
blocks[i] = block
receipts[i] = receipt
parent = block
snaps = statedb.Snaps()
}
return blocks, receipts, proofs, keyvals
}

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
5 changes: 5 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ var (
// than init code size limit.
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")

// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
// funds to cover the transfer, but not enough to pay for witness access/modification
// costs for the transaction
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")

// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
Expand Down
27 changes: 22 additions & 5 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,15 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error {
}

// deriveHash computes the state root according to the genesis specification.
func (ga *GenesisAlloc) deriveHash() (common.Hash, error) {
func (ga *GenesisAlloc) deriveHash(cfg *params.ChainConfig, timestamp uint64) (common.Hash, error) {
// Create an ephemeral in-memory database for computing hash,
// all the derived states will be discarded to not pollute disk.
db := state.NewDatabase(rawdb.NewMemoryDatabase())
// XXX check this is the case
// TODO remove the nil config check once we have rebased, it should never be nil
if cfg != nil && cfg.IsVerkle(big.NewInt(int64(0)), timestamp) {
db.EndVerkleTransition()
}
statedb, err := state.New(types.EmptyRootHash, db, nil)
if err != nil {
return common.Hash{}, err
Expand All @@ -143,11 +148,17 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) {
// flush is very similar with deriveHash, but the main difference is
// all the generated states will be persisted into the given database.
// Also, the genesis state specification will be flushed as well.
func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error {
func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash, cfg *params.ChainConfig) error {
statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil)
if err != nil {
return err
}

// End the verkle conversion at genesis if the fork block is 0
if triedb.Verkle() {
statedb.Database().EndVerkleTransition()
}

for addr, account := range *ga {
statedb.AddBalance(addr, account.Balance)
statedb.SetCode(addr, account.Code)
Expand Down Expand Up @@ -179,11 +190,16 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas
// hash and commits it into the provided trie database.
func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error {
var alloc GenesisAlloc
var config *params.ChainConfig
blob := rawdb.ReadGenesisStateSpec(db, blockhash)
if len(blob) != 0 {
if err := alloc.UnmarshalJSON(blob); err != nil {
return err
}
config = rawdb.ReadChainConfig(db, blockhash)
if config == nil {
return errors.New("genesis config missing from db")
}
} else {
// Genesis allocation is missing and there are several possibilities:
// the node is legacy which doesn't persist the genesis allocation or
Expand All @@ -201,11 +217,12 @@ func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash comm
}
if genesis != nil {
alloc = genesis.Alloc
config = genesis.Config
} else {
return errors.New("not found")
}
}
return alloc.flush(db, triedb, blockhash)
return alloc.flush(db, triedb, blockhash, config)
}

// GenesisAccount is an account in the state of the genesis block.
Expand Down Expand Up @@ -440,7 +457,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {

// ToBlock returns the genesis block according to genesis specification.
func (g *Genesis) ToBlock() *types.Block {
root, err := g.Alloc.deriveHash()
root, err := g.Alloc.deriveHash(g.Config, g.Timestamp)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -515,7 +532,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block
// All the checks has passed, flush the states derived from the genesis
// specification as well as the specification itself into the provided
// database.
if err := g.Alloc.flush(db, triedb, block.Hash()); err != nil {
if err := g.Alloc.flush(db, triedb, block.Hash(), g.Config); err != nil {
return nil, err
}
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), block.Difficulty())
Expand Down
2 changes: 1 addition & 1 deletion core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func TestReadWriteGenesisAlloc(t *testing.T) {
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
{2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}},
}
hash, _ = alloc.deriveHash()
hash, _ = alloc.deriveHash(&params.ChainConfig{}, 0)
)
blob, _ := json.Marshal(alloc)
rawdb.WriteGenesisStateSpec(db, hash, blob)
Expand Down
Loading