mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
Merge branch 'master' of https://github.com/the-shadow-coder/go-ethereum into fix/txlookup
This commit is contained in:
commit
90add30d0f
40 changed files with 634 additions and 1778 deletions
39
.github/workflows/validate_pr.yml
vendored
39
.github/workflows/validate_pr.yml
vendored
|
|
@ -8,6 +8,45 @@ jobs:
|
||||||
validate-pr:
|
validate-pr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check for Spam PR
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const prTitle = context.payload.pull_request.title;
|
||||||
|
const spamRegex = /^(feat|chore|fix)(\(.*\))?\s*:/i;
|
||||||
|
|
||||||
|
if (spamRegex.test(prTitle)) {
|
||||||
|
// Leave a comment explaining why
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
body: `## PR Closed as Spam
|
||||||
|
|
||||||
|
This PR was automatically closed because the title format \`feat:\`, \`fix:\`, or \`chore:\` is commonly associated with spam contributions.
|
||||||
|
|
||||||
|
If this is a legitimate contribution, please:
|
||||||
|
1. Review our contribution guidelines
|
||||||
|
2. Use the correct PR title format: \`directory, ...: description\`
|
||||||
|
3. Open a new PR with the proper title format
|
||||||
|
|
||||||
|
Thank you for your understanding.`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the PR
|
||||||
|
await github.rest.pulls.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
pull_number: context.payload.pull_request.number,
|
||||||
|
state: 'closed'
|
||||||
|
});
|
||||||
|
|
||||||
|
core.setFailed('PR closed as spam due to suspicious title format');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ PR passed spam check');
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,23 @@ var _ = (*executableDataMarshaling)(nil)
|
||||||
// MarshalJSON marshals as JSON.
|
// MarshalJSON marshals as JSON.
|
||||||
func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||||
type ExecutableData struct {
|
type ExecutableData struct {
|
||||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
|
||||||
}
|
}
|
||||||
var enc ExecutableData
|
var enc ExecutableData
|
||||||
enc.ParentHash = e.ParentHash
|
enc.ParentHash = e.ParentHash
|
||||||
|
|
@ -59,31 +58,29 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||||
enc.Withdrawals = e.Withdrawals
|
enc.Withdrawals = e.Withdrawals
|
||||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||||
enc.ExecutionWitness = e.ExecutionWitness
|
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals from JSON.
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||||
type ExecutableData struct {
|
type ExecutableData struct {
|
||||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
|
||||||
}
|
}
|
||||||
var dec ExecutableData
|
var dec ExecutableData
|
||||||
if err := json.Unmarshal(input, &dec); err != nil {
|
if err := json.Unmarshal(input, &dec); err != nil {
|
||||||
|
|
@ -157,8 +154,5 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||||
if dec.ExcessBlobGas != nil {
|
if dec.ExcessBlobGas != nil {
|
||||||
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
|
||||||
}
|
}
|
||||||
if dec.ExecutionWitness != nil {
|
|
||||||
e.ExecutionWitness = dec.ExecutionWitness
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,24 +73,23 @@ type payloadAttributesMarshaling struct {
|
||||||
|
|
||||||
// ExecutableData is the data necessary to execute an EL payload.
|
// ExecutableData is the data necessary to execute an EL payload.
|
||||||
type ExecutableData struct {
|
type ExecutableData struct {
|
||||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||||
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
LogsBloom []byte `json:"logsBloom" gencodec:"required"`
|
||||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||||
Number uint64 `json:"blockNumber" gencodec:"required"`
|
Number uint64 `json:"blockNumber" gencodec:"required"`
|
||||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||||
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
Timestamp uint64 `json:"timestamp" gencodec:"required"`
|
||||||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||||
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
ExcessBlobGas *uint64 `json:"excessBlobGas"`
|
||||||
ExecutionWitness *types.ExecutionWitness `json:"executionWitness,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON type overrides for executableData.
|
// JSON type overrides for executableData.
|
||||||
|
|
@ -316,8 +315,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
||||||
RequestsHash: requestsHash,
|
RequestsHash: requestsHash,
|
||||||
}
|
}
|
||||||
return types.NewBlockWithHeader(header).
|
return types.NewBlockWithHeader(header).
|
||||||
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).
|
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}),
|
||||||
WithWitness(data.ExecutionWitness),
|
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,24 +323,23 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
|
||||||
// fields from the given block. It assumes the given block is post-merge block.
|
// fields from the given block. It assumes the given block is post-merge block.
|
||||||
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
|
func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope {
|
||||||
data := &ExecutableData{
|
data := &ExecutableData{
|
||||||
BlockHash: block.Hash(),
|
BlockHash: block.Hash(),
|
||||||
ParentHash: block.ParentHash(),
|
ParentHash: block.ParentHash(),
|
||||||
FeeRecipient: block.Coinbase(),
|
FeeRecipient: block.Coinbase(),
|
||||||
StateRoot: block.Root(),
|
StateRoot: block.Root(),
|
||||||
Number: block.NumberU64(),
|
Number: block.NumberU64(),
|
||||||
GasLimit: block.GasLimit(),
|
GasLimit: block.GasLimit(),
|
||||||
GasUsed: block.GasUsed(),
|
GasUsed: block.GasUsed(),
|
||||||
BaseFeePerGas: block.BaseFee(),
|
BaseFeePerGas: block.BaseFee(),
|
||||||
Timestamp: block.Time(),
|
Timestamp: block.Time(),
|
||||||
ReceiptsRoot: block.ReceiptHash(),
|
ReceiptsRoot: block.ReceiptHash(),
|
||||||
LogsBloom: block.Bloom().Bytes(),
|
LogsBloom: block.Bloom().Bytes(),
|
||||||
Transactions: encodeTransactions(block.Transactions()),
|
Transactions: encodeTransactions(block.Transactions()),
|
||||||
Random: block.MixDigest(),
|
Random: block.MixDigest(),
|
||||||
ExtraData: block.Extra(),
|
ExtraData: block.Extra(),
|
||||||
Withdrawals: block.Withdrawals(),
|
Withdrawals: block.Withdrawals(),
|
||||||
BlobGasUsed: block.BlobGasUsed(),
|
BlobGasUsed: block.BlobGasUsed(),
|
||||||
ExcessBlobGas: block.ExcessBlobGas(),
|
ExcessBlobGas: block.ExcessBlobGas(),
|
||||||
ExecutionWitness: block.ExecutionWitness(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add blobs.
|
// Add blobs.
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ func (s *HeadSync) Process(requester request.Requester, events []request.Event)
|
||||||
delete(s.serverHeads, event.Server)
|
delete(s.serverHeads, event.Server)
|
||||||
delete(s.unvalidatedOptimistic, event.Server)
|
delete(s.unvalidatedOptimistic, event.Server)
|
||||||
delete(s.unvalidatedFinality, event.Server)
|
delete(s.unvalidatedFinality, event.Server)
|
||||||
|
delete(s.reqFinalityEpoch, event.Server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,8 +251,6 @@ func init() {
|
||||||
utils.ShowDeprecated,
|
utils.ShowDeprecated,
|
||||||
// See snapshot.go
|
// See snapshot.go
|
||||||
snapshotCommand,
|
snapshotCommand,
|
||||||
// See verkle.go
|
|
||||||
verkleCommand,
|
|
||||||
}
|
}
|
||||||
if logTestCommand != nil {
|
if logTestCommand != nil {
|
||||||
app.Commands = append(app.Commands, logTestCommand)
|
app.Commands = append(app.Commands, logTestCommand)
|
||||||
|
|
|
||||||
|
|
@ -1,214 +0,0 @@
|
||||||
// Copyright 2022 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-verkle"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
zero [32]byte
|
|
||||||
|
|
||||||
verkleCommand = &cli.Command{
|
|
||||||
Name: "verkle",
|
|
||||||
Usage: "A set of experimental verkle tree management commands",
|
|
||||||
Description: "",
|
|
||||||
Subcommands: []*cli.Command{
|
|
||||||
{
|
|
||||||
Name: "verify",
|
|
||||||
Usage: "verify the conversion of a MPT into a verkle tree",
|
|
||||||
ArgsUsage: "<root>",
|
|
||||||
Action: verifyVerkle,
|
|
||||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: `
|
|
||||||
geth verkle verify <state-root>
|
|
||||||
This command takes a root commitment and attempts to rebuild the tree.
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "dump",
|
|
||||||
Usage: "Dump a verkle tree to a DOT file",
|
|
||||||
ArgsUsage: "<root> <key1> [<key 2> ...]",
|
|
||||||
Action: expandVerkle,
|
|
||||||
Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
|
|
||||||
Description: `
|
|
||||||
geth verkle dump <state-root> <key 1> [<key 2> ...]
|
|
||||||
This command will produce a dot file representing the tree, rooted at <root>.
|
|
||||||
in which key1, key2, ... are expanded.
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// recurse into each child to ensure they can be loaded from the db. The tree isn't rebuilt
|
|
||||||
// (only its nodes are loaded) so there is no need to flush them, the garbage collector should
|
|
||||||
// take care of that for us.
|
|
||||||
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()
|
|
||||||
|
|
||||||
if bytes.Equal(childC[:], zero[:]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
childS, err := resolver(childC[:])
|
|
||||||
if err != nil {
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err)
|
|
||||||
}
|
|
||||||
if err := checkChildren(childN, resolver); err != nil {
|
|
||||||
return fmt.Errorf("%x%w", i, err) // write the path to the erroring node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *verkle.LeafNode:
|
|
||||||
// sanity check: ensure at least one value is non-zero
|
|
||||||
|
|
||||||
for i := 0; i < verkle.NodeWidth; i++ {
|
|
||||||
if len(node.Value(i)) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("both balance and nonce are 0")
|
|
||||||
case verkle.Empty:
|
|
||||||
// nothing to do
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type encountered %v", root)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyVerkle(ctx *cli.Context) error {
|
|
||||||
stack, _ := makeConfigNode(ctx)
|
|
||||||
defer stack.Close()
|
|
||||||
|
|
||||||
chaindb := utils.MakeChainDatabase(ctx, stack, true)
|
|
||||||
defer chaindb.Close()
|
|
||||||
headBlock := rawdb.ReadHeadBlock(chaindb)
|
|
||||||
if headBlock == nil {
|
|
||||||
log.Error("Failed to load head block")
|
|
||||||
return errors.New("no head block")
|
|
||||||
}
|
|
||||||
if ctx.NArg() > 1 {
|
|
||||||
log.Error("Too many arguments given")
|
|
||||||
return errors.New("too many arguments")
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
rootC common.Hash
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if ctx.NArg() == 1 {
|
|
||||||
rootC, err = parseRoot(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to resolve state root", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info("Rebuilding the tree", "root", rootC)
|
|
||||||
} else {
|
|
||||||
rootC = headBlock.Root()
|
|
||||||
log.Info("Rebuilding the tree", "root", rootC, "number", headBlock.NumberU64())
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedRoot, err := chaindb.Get(rootC[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
root, err := verkle.ParseNode(serializedRoot, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkChildren(root, chaindb.Get); err != nil {
|
|
||||||
log.Error("Could not rebuild the tree from the database", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Tree was rebuilt from the database")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandVerkle(ctx *cli.Context) error {
|
|
||||||
stack, _ := makeConfigNode(ctx)
|
|
||||||
defer stack.Close()
|
|
||||||
|
|
||||||
chaindb := utils.MakeChainDatabase(ctx, stack, true)
|
|
||||||
defer chaindb.Close()
|
|
||||||
var (
|
|
||||||
rootC common.Hash
|
|
||||||
keylist [][]byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if ctx.NArg() >= 2 {
|
|
||||||
rootC, err = parseRoot(ctx.Args().First())
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to resolve state root", "error", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keylist = make([][]byte, 0, ctx.Args().Len()-1)
|
|
||||||
args := ctx.Args().Slice()
|
|
||||||
for i := range args[1:] {
|
|
||||||
key, err := hex.DecodeString(args[i+1])
|
|
||||||
log.Info("decoded key", "arg", args[i+1], "key", key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error decoding key #%d: %w", i+1, err)
|
|
||||||
}
|
|
||||||
keylist = append(keylist, key)
|
|
||||||
}
|
|
||||||
log.Info("Rebuilding the tree", "root", rootC)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("usage: %s root key1 [key 2...]", ctx.App.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedRoot, err := chaindb.Get(rootC[:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
root, err := verkle.ParseNode(serializedRoot, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, key := range keylist {
|
|
||||||
log.Info("Reading key", "index", i, "key", key)
|
|
||||||
root.Get(key, chaindb.Get)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.WriteFile("dump.dot", []byte(verkle.ToDot(root)), 0600); err != nil {
|
|
||||||
log.Error("Failed to dump file", "err", err)
|
|
||||||
} else {
|
|
||||||
log.Info("Tree was dumped to file", "file", "dump.dot")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -14,13 +14,11 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/consensys/gnark-crypto v0.18.1 // indirect
|
github.com/consensys/gnark-crypto v0.18.1 // indirect
|
||||||
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
|
|
||||||
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
|
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||||
github.com/emicklei/dot v1.6.2 // indirect
|
github.com/emicklei/dot v1.6.2 // indirect
|
||||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect
|
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect
|
||||||
github.com/ethereum/go-verkle v0.2.2 // indirect
|
|
||||||
github.com/ferranbt/fastssz v0.1.4 // indirect
|
github.com/ferranbt/fastssz v0.1.4 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/gofrs/flock v0.12.1 // indirect
|
github.com/gofrs/flock v0.12.1 // indirect
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@ github.com/consensys/gnark-crypto v0.18.1 h1:RyLV6UhPRoYYzaFnPQA4qK3DyuDgkTgskDd
|
||||||
github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
|
github.com/consensys/gnark-crypto v0.18.1/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
|
||||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||||
|
|
@ -46,8 +44,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3
|
||||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
|
||||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
|
||||||
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
||||||
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
|
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
|
||||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||||
|
|
|
||||||
|
|
@ -365,46 +365,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
|
||||||
header.Root = state.IntermediateRoot(true)
|
header.Root = state.IntermediateRoot(true)
|
||||||
|
|
||||||
// Assemble the final block.
|
// Assemble the final block.
|
||||||
block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
|
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
|
||||||
|
|
||||||
// Create the block witness and attach to block.
|
|
||||||
// This step needs to happen as late as possible to catch all access events.
|
|
||||||
if chain.Config().IsVerkle(header.Number, header.Time) {
|
|
||||||
keys := state.AccessEvents().Keys()
|
|
||||||
|
|
||||||
// Open the pre-tree to prove the pre-state against
|
|
||||||
parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1)
|
|
||||||
if parent == nil {
|
|
||||||
return nil, fmt.Errorf("nil parent header for block %d", header.Number)
|
|
||||||
}
|
|
||||||
preTrie, err := state.Database().OpenTrie(parent.Root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error opening pre-state tree root: %w", err)
|
|
||||||
}
|
|
||||||
postTrie := state.GetTrie()
|
|
||||||
if postTrie == nil {
|
|
||||||
return nil, errors.New("post-state tree is not available")
|
|
||||||
}
|
|
||||||
vktPreTrie, okpre := preTrie.(*trie.VerkleTrie)
|
|
||||||
vktPostTrie, okpost := postTrie.(*trie.VerkleTrie)
|
|
||||||
|
|
||||||
// The witness is only attached iff both parent and current block are
|
|
||||||
// using verkle tree.
|
|
||||||
if okpre && okpost {
|
|
||||||
if len(keys) > 0 {
|
|
||||||
verkleProof, stateDiff, err := vktPreTrie.Proof(vktPostTrie, keys)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating verkle proof for block %d: %w", header.Number, err)
|
|
||||||
}
|
|
||||||
block = block.WithWitness(&types.ExecutionWitness{
|
|
||||||
StateDiff: stateDiff,
|
|
||||||
VerkleProof: verkleProof,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return block, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seal generates a new sealing request for the given input block and pushes
|
// Seal generates a new sealing request for the given input block and pushes
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err err
|
||||||
// PromptConfirm displays the given prompt to the user and requests a boolean
|
// PromptConfirm displays the given prompt to the user and requests a boolean
|
||||||
// choice to be made, returning that choice.
|
// choice to be made, returning that choice.
|
||||||
func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) {
|
func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) {
|
||||||
input, err := p.Prompt(prompt + " [y/n] ")
|
input, err := p.PromptInput(prompt + " [y/n] ")
|
||||||
if len(input) > 0 && strings.EqualFold(input[:1], "y") {
|
if len(input) > 0 && strings.EqualFold(input[:1], "y") {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,15 +45,12 @@ var zeroTreeIndex uint256.Int
|
||||||
type AccessEvents struct {
|
type AccessEvents struct {
|
||||||
branches map[branchAccessKey]mode
|
branches map[branchAccessKey]mode
|
||||||
chunks map[chunkAccessKey]mode
|
chunks map[chunkAccessKey]mode
|
||||||
|
|
||||||
pointCache *utils.PointCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents {
|
func NewAccessEvents() *AccessEvents {
|
||||||
return &AccessEvents{
|
return &AccessEvents{
|
||||||
branches: make(map[branchAccessKey]mode),
|
branches: make(map[branchAccessKey]mode),
|
||||||
chunks: make(map[chunkAccessKey]mode),
|
chunks: make(map[chunkAccessKey]mode),
|
||||||
pointCache: pointCache,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,8 +72,11 @@ func (ae *AccessEvents) Keys() [][]byte {
|
||||||
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
|
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
|
||||||
keys := make([][]byte, 0, len(ae.chunks))
|
keys := make([][]byte, 0, len(ae.chunks))
|
||||||
for chunk := range ae.chunks {
|
for chunk := range ae.chunks {
|
||||||
basePoint := ae.pointCache.Get(chunk.addr[:])
|
var offset [32]byte
|
||||||
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
|
treeIndexBytes := chunk.treeIndex.Bytes32()
|
||||||
|
copy(offset[:31], treeIndexBytes[1:])
|
||||||
|
offset[31] = chunk.leafKey
|
||||||
|
key := bintrie.GetBinaryTreeKey(chunk.addr, offset[:])
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
return keys
|
return keys
|
||||||
|
|
@ -84,9 +84,8 @@ func (ae *AccessEvents) Keys() [][]byte {
|
||||||
|
|
||||||
func (ae *AccessEvents) Copy() *AccessEvents {
|
func (ae *AccessEvents) Copy() *AccessEvents {
|
||||||
cpy := &AccessEvents{
|
cpy := &AccessEvents{
|
||||||
branches: maps.Clone(ae.branches),
|
branches: maps.Clone(ae.branches),
|
||||||
chunks: maps.Clone(ae.chunks),
|
chunks: maps.Clone(ae.chunks),
|
||||||
pointCache: ae.pointCache,
|
|
||||||
}
|
}
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
@ -95,12 +94,12 @@ func (ae *AccessEvents) Copy() *AccessEvents {
|
||||||
// member fields of an account.
|
// member fields of an account.
|
||||||
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
|
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableGas uint64) uint64 {
|
||||||
var gas uint64 // accumulate the consumed gas
|
var gas uint64 // accumulate the consumed gas
|
||||||
consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
|
consumed, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, isWrite, availableGas)
|
||||||
if consumed < expected {
|
if consumed < expected {
|
||||||
return expected
|
return expected
|
||||||
}
|
}
|
||||||
gas += consumed
|
gas += consumed
|
||||||
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas-consumed)
|
consumed, expected = ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, isWrite, availableGas-consumed)
|
||||||
if consumed < expected {
|
if consumed < expected {
|
||||||
return expected + gas
|
return expected + gas
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +111,7 @@ func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool, availableG
|
||||||
// cold member fields of an account, that need to be touched when making a message
|
// cold member fields of an account, that need to be touched when making a message
|
||||||
// call to that account.
|
// call to that account.
|
||||||
func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
|
func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas uint64) uint64 {
|
||||||
_, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
|
_, expected := ae.touchAddressAndChargeGas(destination, zeroTreeIndex, bintrie.BasicDataLeafKey, false, availableGas)
|
||||||
if expected == 0 {
|
if expected == 0 {
|
||||||
expected = params.WarmStorageReadCostEIP2929
|
expected = params.WarmStorageReadCostEIP2929
|
||||||
}
|
}
|
||||||
|
|
@ -122,11 +121,11 @@ func (ae *AccessEvents) MessageCallGas(destination common.Address, availableGas
|
||||||
// ValueTransferGas returns the gas to be charged for each of the currently
|
// ValueTransferGas returns the gas to be charged for each of the currently
|
||||||
// cold balance member fields of the caller and the callee accounts.
|
// cold balance member fields of the caller and the callee accounts.
|
||||||
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
|
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address, availableGas uint64) uint64 {
|
||||||
_, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
|
_, expected1 := ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, availableGas)
|
||||||
if expected1 > availableGas {
|
if expected1 > availableGas {
|
||||||
return expected1
|
return expected1
|
||||||
}
|
}
|
||||||
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas-expected1)
|
_, expected2 := ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, availableGas-expected1)
|
||||||
if expected1+expected2 == 0 {
|
if expected1+expected2 == 0 {
|
||||||
return params.WarmStorageReadCostEIP2929
|
return params.WarmStorageReadCostEIP2929
|
||||||
}
|
}
|
||||||
|
|
@ -138,8 +137,8 @@ func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address,
|
||||||
// address collision is done before the transfer, and so no write
|
// address collision is done before the transfer, and so no write
|
||||||
// are guaranteed to happen at this point.
|
// are guaranteed to happen at this point.
|
||||||
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
|
func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, availableGas uint64) uint64 {
|
||||||
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, false, availableGas)
|
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, false, availableGas)
|
||||||
_, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, false, availableGas-consumed)
|
_, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, false, availableGas-consumed)
|
||||||
return expected1 + expected2
|
return expected1 + expected2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,9 +146,9 @@ func (ae *AccessEvents) ContractCreatePreCheckGas(addr common.Address, available
|
||||||
// a contract creation.
|
// a contract creation.
|
||||||
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
|
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas uint64) (uint64, uint64) {
|
||||||
var gas uint64
|
var gas uint64
|
||||||
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, true, availableGas)
|
consumed, expected1 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, availableGas)
|
||||||
gas += consumed
|
gas += consumed
|
||||||
consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, true, availableGas-consumed)
|
consumed, expected2 := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, true, availableGas-consumed)
|
||||||
gas += consumed
|
gas += consumed
|
||||||
return gas, expected1 + expected2
|
return gas, expected1 + expected2
|
||||||
}
|
}
|
||||||
|
|
@ -157,20 +156,20 @@ func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, availableGas
|
||||||
// AddTxOrigin adds the member fields of the sender account to the access event list,
|
// AddTxOrigin adds the member fields of the sender account to the access event list,
|
||||||
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
||||||
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
|
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
|
||||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BasicDataLeafKey, true, gomath.MaxUint64)
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, bintrie.BasicDataLeafKey, true, gomath.MaxUint64)
|
||||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeHashLeafKey, false, gomath.MaxUint64)
|
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, bintrie.CodeHashLeafKey, false, gomath.MaxUint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTxDestination adds the member fields of the sender account to the access event list,
|
// AddTxDestination adds the member fields of the sender account to the access event list,
|
||||||
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
||||||
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
|
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue, doesntExist bool) {
|
||||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, sendsValue, gomath.MaxUint64)
|
||||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
|
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, doesntExist, gomath.MaxUint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SlotGas returns the amount of gas to be charged for a cold storage access.
|
// SlotGas returns the amount of gas to be charged for a cold storage access.
|
||||||
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
|
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
|
||||||
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
|
treeIndex, subIndex := bintrie.StorageIndex(slot.Bytes())
|
||||||
_, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
|
_, expected := ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite, availableGas)
|
||||||
if expected == 0 && chargeWarmCosts {
|
if expected == 0 && chargeWarmCosts {
|
||||||
expected = params.WarmStorageReadCostEIP2929
|
expected = params.WarmStorageReadCostEIP2929
|
||||||
|
|
@ -313,7 +312,7 @@ func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC,
|
||||||
// Note that an access in write mode implies an access in read mode, whereas an
|
// Note that an access in write mode implies an access in read mode, whereas an
|
||||||
// access in read mode does not imply an access in write mode.
|
// access in read mode does not imply an access in write mode.
|
||||||
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
|
func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
|
||||||
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BasicDataLeafKey, isWrite, availableGas)
|
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.BasicDataLeafKey, isWrite, availableGas)
|
||||||
if expected == 0 && chargeWarmCosts {
|
if expected == 0 && chargeWarmCosts {
|
||||||
if availableGas < params.WarmStorageReadCostEIP2929 {
|
if availableGas < params.WarmStorageReadCostEIP2929 {
|
||||||
return availableGas
|
return availableGas
|
||||||
|
|
@ -329,7 +328,7 @@ func (ae *AccessEvents) BasicDataGas(addr common.Address, isWrite bool, availabl
|
||||||
// Note that an access in write mode implies an access in read mode, whereas an access in
|
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||||
// read mode does not imply an access in write mode.
|
// read mode does not imply an access in write mode.
|
||||||
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
|
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool, availableGas uint64, chargeWarmCosts bool) uint64 {
|
||||||
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeHashLeafKey, isWrite, availableGas)
|
_, expected := ae.touchAddressAndChargeGas(addr, zeroTreeIndex, bintrie.CodeHashLeafKey, isWrite, availableGas)
|
||||||
if expected == 0 && chargeWarmCosts {
|
if expected == 0 && chargeWarmCosts {
|
||||||
if availableGas < params.WarmStorageReadCostEIP2929 {
|
if availableGas < params.WarmStorageReadCostEIP2929 {
|
||||||
return availableGas
|
return availableGas
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -38,7 +37,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountHeaderGas(t *testing.T) {
|
func TestAccountHeaderGas(t *testing.T) {
|
||||||
ae := NewAccessEvents(utils.NewPointCache(1024))
|
ae := NewAccessEvents()
|
||||||
|
|
||||||
// Check cold read cost
|
// Check cold read cost
|
||||||
gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
|
gas := ae.BasicDataGas(testAddr, false, math.MaxUint64, false)
|
||||||
|
|
@ -93,7 +92,7 @@ func TestAccountHeaderGas(t *testing.T) {
|
||||||
// TestContractCreateInitGas checks that the gas cost of contract creation is correctly
|
// TestContractCreateInitGas checks that the gas cost of contract creation is correctly
|
||||||
// calculated.
|
// calculated.
|
||||||
func TestContractCreateInitGas(t *testing.T) {
|
func TestContractCreateInitGas(t *testing.T) {
|
||||||
ae := NewAccessEvents(utils.NewPointCache(1024))
|
ae := NewAccessEvents()
|
||||||
|
|
||||||
var testAddr [20]byte
|
var testAddr [20]byte
|
||||||
for i := byte(0); i < 20; i++ {
|
for i := byte(0); i < 20; i++ {
|
||||||
|
|
@ -116,7 +115,7 @@ func TestContractCreateInitGas(t *testing.T) {
|
||||||
// TestMessageCallGas checks that the gas cost of message calls is correctly
|
// TestMessageCallGas checks that the gas cost of message calls is correctly
|
||||||
// calculated.
|
// calculated.
|
||||||
func TestMessageCallGas(t *testing.T) {
|
func TestMessageCallGas(t *testing.T) {
|
||||||
ae := NewAccessEvents(utils.NewPointCache(1024))
|
ae := NewAccessEvents()
|
||||||
|
|
||||||
// Check cold read cost, without a value
|
// Check cold read cost, without a value
|
||||||
gas := ae.MessageCallGas(testAddr, math.MaxUint64)
|
gas := ae.MessageCallGas(testAddr, math.MaxUint64)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,9 +40,6 @@ const (
|
||||||
|
|
||||||
// Cache size granted for caching clean code.
|
// Cache size granted for caching clean code.
|
||||||
codeCacheSize = 256 * 1024 * 1024
|
codeCacheSize = 256 * 1024 * 1024
|
||||||
|
|
||||||
// Number of address->curve point associations to keep.
|
|
||||||
pointCacheSize = 4096
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database wraps access to tries and contract code.
|
// Database wraps access to tries and contract code.
|
||||||
|
|
@ -57,9 +53,6 @@ type Database interface {
|
||||||
// OpenStorageTrie opens the storage trie of an account.
|
// OpenStorageTrie opens the storage trie of an account.
|
||||||
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
|
OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error)
|
||||||
|
|
||||||
// PointCache returns the cache holding points used in verkle tree key computation
|
|
||||||
PointCache() *utils.PointCache
|
|
||||||
|
|
||||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||||
TrieDB() *triedb.Database
|
TrieDB() *triedb.Database
|
||||||
|
|
||||||
|
|
@ -161,7 +154,6 @@ type CachingDB struct {
|
||||||
snap *snapshot.Tree
|
snap *snapshot.Tree
|
||||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||||
codeSizeCache *lru.Cache[common.Hash, int]
|
codeSizeCache *lru.Cache[common.Hash, int]
|
||||||
pointCache *utils.PointCache
|
|
||||||
|
|
||||||
// Transition-specific fields
|
// Transition-specific fields
|
||||||
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
|
TransitionStatePerRoot *lru.Cache[common.Hash, *overlay.TransitionState]
|
||||||
|
|
@ -175,7 +167,6 @@ func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
|
||||||
snap: snap,
|
snap: snap,
|
||||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||||
pointCache: utils.NewPointCache(pointCacheSize),
|
|
||||||
TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000),
|
TransitionStatePerRoot: lru.NewCache[common.Hash, *overlay.TransitionState](1000),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,7 +202,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
|
||||||
}
|
}
|
||||||
// Configure the trie reader, which is expected to be available as the
|
// Configure the trie reader, which is expected to be available as the
|
||||||
// gatekeeper unless the state is corrupted.
|
// gatekeeper unless the state is corrupted.
|
||||||
tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache)
|
tr, err := newTrieReader(stateRoot, db.triedb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -289,11 +280,6 @@ func (db *CachingDB) TrieDB() *triedb.Database {
|
||||||
return db.triedb
|
return db.triedb
|
||||||
}
|
}
|
||||||
|
|
||||||
// PointCache returns the cache of evaluated curve points.
|
|
||||||
func (db *CachingDB) PointCache() *utils.PointCache {
|
|
||||||
return db.pointCache
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot returns the underlying state snapshot.
|
// Snapshot returns the underlying state snapshot.
|
||||||
func (db *CachingDB) Snapshot() *snapshot.Tree {
|
func (db *CachingDB) Snapshot() *snapshot.Tree {
|
||||||
return db.snap
|
return db.snap
|
||||||
|
|
@ -304,8 +290,6 @@ func mustCopyTrie(t Trie) Trie {
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
case *trie.StateTrie:
|
case *trie.StateTrie:
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
case *trie.VerkleTrie:
|
|
||||||
return t.Copy()
|
|
||||||
case *transitiontrie.TransitionTrie:
|
case *transitiontrie.TransitionTrie:
|
||||||
return t.Copy()
|
return t.Copy()
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
)
|
)
|
||||||
|
|
@ -105,7 +104,6 @@ type HistoricDB struct {
|
||||||
triedb *triedb.Database
|
triedb *triedb.Database
|
||||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||||
codeSizeCache *lru.Cache[common.Hash, int]
|
codeSizeCache *lru.Cache[common.Hash, int]
|
||||||
pointCache *utils.PointCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHistoricDatabase creates a historic state database.
|
// NewHistoricDatabase creates a historic state database.
|
||||||
|
|
@ -115,7 +113,6 @@ func NewHistoricDatabase(disk ethdb.KeyValueStore, triedb *triedb.Database) *His
|
||||||
triedb: triedb,
|
triedb: triedb,
|
||||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||||
pointCache: utils.NewPointCache(pointCacheSize),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,11 +136,6 @@ func (db *HistoricDB) OpenStorageTrie(stateRoot common.Hash, address common.Addr
|
||||||
return nil, errors.New("not implemented")
|
return nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PointCache returns the cache holding points used in verkle tree key computation
|
|
||||||
func (db *HistoricDB) PointCache() *utils.PointCache {
|
|
||||||
return db.pointCache
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||||
func (db *HistoricDB) TrieDB() *triedb.Database {
|
func (db *HistoricDB) TrieDB() *triedb.Database {
|
||||||
return db.triedb
|
return db.triedb
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/database"
|
"github.com/ethereum/go-ethereum/triedb/database"
|
||||||
)
|
)
|
||||||
|
|
@ -267,7 +266,7 @@ type trieReader struct {
|
||||||
|
|
||||||
// newTrieReader constructs a trie reader of the specific state. An error will be
|
// newTrieReader constructs a trie reader of the specific state. An error will be
|
||||||
// returned if the associated trie specified by root is not existent.
|
// returned if the associated trie specified by root is not existent.
|
||||||
func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache) (*trieReader, error) {
|
func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
|
||||||
var (
|
var (
|
||||||
tr Trie
|
tr Trie
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
"github.com/ethereum/go-ethereum/trie/transitiontrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
@ -498,8 +499,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s.trie.(type) {
|
switch s.trie.(type) {
|
||||||
case *trie.VerkleTrie:
|
case *bintrie.BinaryTrie:
|
||||||
// Verkle uses only one tree, and the copy has already been
|
// UBT uses only one tree, and the copy has already been
|
||||||
// made in mustCopyTrie.
|
// made in mustCopyTrie.
|
||||||
obj.trie = db.trie
|
obj.trie = db.trie
|
||||||
case *transitiontrie.TransitionTrie:
|
case *transitiontrie.TransitionTrie:
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
@ -188,7 +187,7 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
||||||
transientStorage: newTransientStorage(),
|
transientStorage: newTransientStorage(),
|
||||||
}
|
}
|
||||||
if db.TrieDB().IsVerkle() {
|
if db.TrieDB().IsVerkle() {
|
||||||
sdb.accessEvents = NewAccessEvents(db.PointCache())
|
sdb.accessEvents = NewAccessEvents()
|
||||||
}
|
}
|
||||||
return sdb, nil
|
return sdb, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1495,11 +1494,6 @@ func (s *StateDB) markUpdate(addr common.Address) {
|
||||||
s.mutations[addr].typ = update
|
s.mutations[addr].typ = update
|
||||||
}
|
}
|
||||||
|
|
||||||
// PointCache returns the point cache used by verkle tree.
|
|
||||||
func (s *StateDB) PointCache() *utils.PointCache {
|
|
||||||
return s.db.PointCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Witness retrieves the current state witness being collected.
|
// Witness retrieves the current state witness being collected.
|
||||||
func (s *StateDB) Witness() *stateless.Witness {
|
func (s *StateDB) Witness() *stateless.Witness {
|
||||||
return s.witness
|
return s.witness
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -133,10 +132,6 @@ func (s *hookedStateDB) AddSlotToAccessList(addr common.Address, slot common.Has
|
||||||
s.inner.AddSlotToAccessList(addr, slot)
|
s.inner.AddSlotToAccessList(addr, slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) PointCache() *utils.PointCache {
|
|
||||||
return s.inner.PointCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *hookedStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
|
func (s *hookedStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) {
|
||||||
s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
|
s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-verkle"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A BlockNonce is a 64-bit hash which proves (combined with the
|
// A BlockNonce is a 64-bit hash which proves (combined with the
|
||||||
|
|
@ -61,13 +60,6 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
|
||||||
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
|
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionWitness represents the witness + proof used in a verkle context,
|
|
||||||
// to provide the ability to execute a block statelessly.
|
|
||||||
type ExecutionWitness struct {
|
|
||||||
StateDiff verkle.StateDiff `json:"stateDiff"`
|
|
||||||
VerkleProof *verkle.VerkleProof `json:"verkleProof"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
||||||
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
|
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
|
||||||
|
|
||||||
|
|
@ -209,11 +201,6 @@ type Block struct {
|
||||||
transactions Transactions
|
transactions Transactions
|
||||||
withdrawals Withdrawals
|
withdrawals Withdrawals
|
||||||
|
|
||||||
// witness is not an encoded part of the block body.
|
|
||||||
// It is held in Block in order for easy relaying to the places
|
|
||||||
// that process it.
|
|
||||||
witness *ExecutionWitness
|
|
||||||
|
|
||||||
// caches
|
// caches
|
||||||
hash atomic.Pointer[common.Hash]
|
hash atomic.Pointer[common.Hash]
|
||||||
size atomic.Uint64
|
size atomic.Uint64
|
||||||
|
|
@ -429,9 +416,6 @@ func (b *Block) BlobGasUsed() *uint64 {
|
||||||
return blobGasUsed
|
return blobGasUsed
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionWitness returns the verkle execution witneess + proof for a block
|
|
||||||
func (b *Block) ExecutionWitness() *ExecutionWitness { return b.witness }
|
|
||||||
|
|
||||||
// Size returns the true RLP encoded storage size of the block, either by encoding
|
// Size returns the true RLP encoded storage size of the block, either by encoding
|
||||||
// and returning it, or returning a previously cached value.
|
// and returning it, or returning a previously cached value.
|
||||||
func (b *Block) Size() uint64 {
|
func (b *Block) Size() uint64 {
|
||||||
|
|
@ -494,7 +478,6 @@ func (b *Block) WithSeal(header *Header) *Block {
|
||||||
transactions: b.transactions,
|
transactions: b.transactions,
|
||||||
uncles: b.uncles,
|
uncles: b.uncles,
|
||||||
withdrawals: b.withdrawals,
|
withdrawals: b.withdrawals,
|
||||||
witness: b.witness,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,7 +489,6 @@ func (b *Block) WithBody(body Body) *Block {
|
||||||
transactions: slices.Clone(body.Transactions),
|
transactions: slices.Clone(body.Transactions),
|
||||||
uncles: make([]*Header, len(body.Uncles)),
|
uncles: make([]*Header, len(body.Uncles)),
|
||||||
withdrawals: slices.Clone(body.Withdrawals),
|
withdrawals: slices.Clone(body.Withdrawals),
|
||||||
witness: b.witness,
|
|
||||||
}
|
}
|
||||||
for i := range body.Uncles {
|
for i := range body.Uncles {
|
||||||
block.uncles[i] = CopyHeader(body.Uncles[i])
|
block.uncles[i] = CopyHeader(body.Uncles[i])
|
||||||
|
|
@ -514,16 +496,6 @@ func (b *Block) WithBody(body Body) *Block {
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Block) WithWitness(witness *ExecutionWitness) *Block {
|
|
||||||
return &Block{
|
|
||||||
header: b.header,
|
|
||||||
transactions: b.transactions,
|
|
||||||
uncles: b.uncles,
|
|
||||||
withdrawals: b.withdrawals,
|
|
||||||
witness: witness,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns the keccak256 hash of b's header.
|
// Hash returns the keccak256 hash of b's header.
|
||||||
// The hash is computed on the first call and cached thereafter.
|
// The hash is computed on the first call and cached thereafter.
|
||||||
func (b *Block) Hash() common.Hash {
|
func (b *Block) Hash() common.Hash {
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ func (evm *EVM) SetJumpDestCache(jumpDests JumpDestCache) {
|
||||||
// This is not threadsafe and should only be done very cautiously.
|
// This is not threadsafe and should only be done very cautiously.
|
||||||
func (evm *EVM) SetTxContext(txCtx TxContext) {
|
func (evm *EVM) SetTxContext(txCtx TxContext) {
|
||||||
if evm.chainRules.IsEIP4762 {
|
if evm.chainRules.IsEIP4762 {
|
||||||
txCtx.AccessEvents = state.NewAccessEvents(evm.StateDB.PointCache())
|
txCtx.AccessEvents = state.NewAccessEvents()
|
||||||
}
|
}
|
||||||
evm.TxContext = txCtx
|
evm.TxContext = txCtx
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -84,9 +83,6 @@ type StateDB interface {
|
||||||
// even if the feature/fork is not active yet
|
// even if the feature/fork is not active yet
|
||||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||||
|
|
||||||
// PointCache returns the point cache used in computations
|
|
||||||
PointCache() *utils.PointCache
|
|
||||||
|
|
||||||
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||||
|
|
||||||
RevertToSnapshot(int)
|
RevertToSnapshot(int)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ type beaconBackfiller struct {
|
||||||
downloader *Downloader // Downloader to direct via this callback implementation
|
downloader *Downloader // Downloader to direct via this callback implementation
|
||||||
success func() // Callback to run on successful sync cycle completion
|
success func() // Callback to run on successful sync cycle completion
|
||||||
filling bool // Flag whether the downloader is backfilling or not
|
filling bool // Flag whether the downloader is backfilling or not
|
||||||
filled *types.Header // Last header filled by the last terminated sync loop
|
|
||||||
started chan struct{} // Notification channel whether the downloader inited
|
started chan struct{} // Notification channel whether the downloader inited
|
||||||
lock sync.Mutex // Mutex protecting the sync lock
|
lock sync.Mutex // Mutex protecting the sync lock
|
||||||
}
|
}
|
||||||
|
|
@ -56,13 +55,15 @@ func (b *beaconBackfiller) suspend() *types.Header {
|
||||||
// If no filling is running, don't waste cycles
|
// If no filling is running, don't waste cycles
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
filling := b.filling
|
filling := b.filling
|
||||||
filled := b.filled
|
|
||||||
started := b.started
|
started := b.started
|
||||||
b.lock.Unlock()
|
b.lock.Unlock()
|
||||||
|
|
||||||
if !filling {
|
if !filling {
|
||||||
|
// Sync cycle was inactive, retrieve and return the latest snap block
|
||||||
|
// as the filled header.
|
||||||
log.Debug("Backfiller was inactive")
|
log.Debug("Backfiller was inactive")
|
||||||
return filled // Return the filled header on the previous sync completion
|
|
||||||
|
return b.downloader.blockchain.CurrentSnapBlock()
|
||||||
}
|
}
|
||||||
// A previous filling should be running, though it may happen that it hasn't
|
// A previous filling should be running, though it may happen that it hasn't
|
||||||
// yet started (being done on a new goroutine). Many concurrent beacon head
|
// yet started (being done on a new goroutine). Many concurrent beacon head
|
||||||
|
|
@ -77,7 +78,6 @@ func (b *beaconBackfiller) suspend() *types.Header {
|
||||||
log.Debug("Backfiller has been suspended")
|
log.Debug("Backfiller has been suspended")
|
||||||
|
|
||||||
// Sync cycle was just terminated, retrieve and return the last filled header.
|
// Sync cycle was just terminated, retrieve and return the last filled header.
|
||||||
// Can't use `filled` as that contains a stale value from before cancellation.
|
|
||||||
return b.downloader.blockchain.CurrentSnapBlock()
|
return b.downloader.blockchain.CurrentSnapBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +92,6 @@ func (b *beaconBackfiller) resume() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.filling = true
|
b.filling = true
|
||||||
b.filled = nil
|
|
||||||
b.started = make(chan struct{})
|
b.started = make(chan struct{})
|
||||||
b.lock.Unlock()
|
b.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -103,7 +102,6 @@ func (b *beaconBackfiller) resume() {
|
||||||
defer func() {
|
defer func() {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
b.filling = false
|
b.filling = false
|
||||||
b.filled = b.downloader.blockchain.CurrentSnapBlock()
|
|
||||||
b.lock.Unlock()
|
b.lock.Unlock()
|
||||||
}()
|
}()
|
||||||
// If the downloader fails, report an error as in beacon chain mode there
|
// If the downloader fails, report an error as in beacon chain mode there
|
||||||
|
|
@ -113,7 +111,7 @@ func (b *beaconBackfiller) resume() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Synchronization succeeded. Since this happens async, notify the outer
|
// Synchronization succeeded. Since this happens async, notify the outer
|
||||||
// context to disable snap syncing and enable transaction propagation.
|
// context to enable transaction propagation.
|
||||||
if b.success != nil {
|
if b.success != nil {
|
||||||
b.success()
|
b.success()
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +186,8 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
||||||
log.Error("Failed to retrieve beacon bounds", "err", err)
|
log.Error("Failed to retrieve beacon bounds", "err", err)
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
log.Debug("Searching beacon ancestor", "local", number, "beaconhead", beaconHead.Number, "beacontail", beaconTail.Number)
|
||||||
|
|
||||||
var linked bool
|
var linked bool
|
||||||
switch d.getMode() {
|
switch d.getMode() {
|
||||||
case ethconfig.FullSync:
|
case ethconfig.FullSync:
|
||||||
|
|
@ -241,6 +241,7 @@ func (d *Downloader) findBeaconAncestor() (uint64, error) {
|
||||||
}
|
}
|
||||||
start = check
|
start = check
|
||||||
}
|
}
|
||||||
|
log.Debug("Found beacon ancestor", "number", start)
|
||||||
return start, nil
|
return start, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ func New(stateDb ethdb.Database, mode ethconfig.SyncMode, mux *event.TypeMux, ch
|
||||||
syncStartBlock: chain.CurrentSnapBlock().Number.Uint64(),
|
syncStartBlock: chain.CurrentSnapBlock().Number.Uint64(),
|
||||||
}
|
}
|
||||||
// Create the post-merge skeleton syncer and start the process
|
// Create the post-merge skeleton syncer and start the process
|
||||||
dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success))
|
dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success), chain)
|
||||||
|
|
||||||
go dl.stateFetcher()
|
go dl.stateFetcher()
|
||||||
return dl
|
return dl
|
||||||
|
|
|
||||||
|
|
@ -207,6 +207,7 @@ type backfiller interface {
|
||||||
type skeleton struct {
|
type skeleton struct {
|
||||||
db ethdb.Database // Database backing the skeleton
|
db ethdb.Database // Database backing the skeleton
|
||||||
filler backfiller // Chain syncer suspended/resumed by head events
|
filler backfiller // Chain syncer suspended/resumed by head events
|
||||||
|
chain chainReader // Underlying block chain
|
||||||
|
|
||||||
peers *peerSet // Set of peers we can sync from
|
peers *peerSet // Set of peers we can sync from
|
||||||
idles map[string]*peerConnection // Set of idle peers in the current sync cycle
|
idles map[string]*peerConnection // Set of idle peers in the current sync cycle
|
||||||
|
|
@ -231,12 +232,19 @@ type skeleton struct {
|
||||||
syncStarting func() // callback triggered after a sync cycle is inited but before started
|
syncStarting func() // callback triggered after a sync cycle is inited but before started
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chainReader wraps the method to retrieve the head of the local chain.
|
||||||
|
type chainReader interface {
|
||||||
|
// CurrentSnapBlock retrieves the head snap block from the local chain.
|
||||||
|
CurrentSnapBlock() *types.Header
|
||||||
|
}
|
||||||
|
|
||||||
// newSkeleton creates a new sync skeleton that tracks a potentially dangling
|
// newSkeleton creates a new sync skeleton that tracks a potentially dangling
|
||||||
// header chain until it's linked into an existing set of blocks.
|
// header chain until it's linked into an existing set of blocks.
|
||||||
func newSkeleton(db ethdb.Database, peers *peerSet, drop peerDropFn, filler backfiller) *skeleton {
|
func newSkeleton(db ethdb.Database, peers *peerSet, drop peerDropFn, filler backfiller, chain chainReader) *skeleton {
|
||||||
sk := &skeleton{
|
sk := &skeleton{
|
||||||
db: db,
|
db: db,
|
||||||
filler: filler,
|
filler: filler,
|
||||||
|
chain: chain,
|
||||||
peers: peers,
|
peers: peers,
|
||||||
drop: drop,
|
drop: drop,
|
||||||
requests: make(map[uint64]*headerRequest),
|
requests: make(map[uint64]*headerRequest),
|
||||||
|
|
@ -354,6 +362,29 @@ func (s *skeleton) Sync(head *types.Header, final *types.Header, force bool) err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// linked returns the flag indicating whether the skeleton has been linked with
|
||||||
|
// the local chain.
|
||||||
|
func (s *skeleton) linked(number uint64, hash common.Hash) bool {
|
||||||
|
linked := rawdb.HasHeader(s.db, hash, number) &&
|
||||||
|
rawdb.HasBody(s.db, hash, number) &&
|
||||||
|
rawdb.HasReceipts(s.db, hash, number)
|
||||||
|
|
||||||
|
// Ensure the skeleton chain links to the local chain below the chain head.
|
||||||
|
// This accounts for edge cases where leftover chain segments above the head
|
||||||
|
// may still link to the skeleton chain. In such cases, synchronization is
|
||||||
|
// likely to fail due to potentially missing segments in the middle.
|
||||||
|
//
|
||||||
|
// You can try to produce the edge case by these steps:
|
||||||
|
// - sync the chain
|
||||||
|
// - debug.setHead(`0x1`)
|
||||||
|
// - kill the geth process (the chain segment will be left with chain head rewound)
|
||||||
|
// - restart
|
||||||
|
if s.chain.CurrentSnapBlock() != nil {
|
||||||
|
linked = linked && s.chain.CurrentSnapBlock().Number.Uint64() >= number
|
||||||
|
}
|
||||||
|
return linked
|
||||||
|
}
|
||||||
|
|
||||||
// sync is the internal version of Sync that executes a single sync cycle, either
|
// sync is the internal version of Sync that executes a single sync cycle, either
|
||||||
// until some termination condition is reached, or until the current cycle merges
|
// until some termination condition is reached, or until the current cycle merges
|
||||||
// with a previously aborted run.
|
// with a previously aborted run.
|
||||||
|
|
@ -378,10 +409,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
|
||||||
|
|
||||||
// If the sync is already done, resume the backfiller. When the loop stops,
|
// If the sync is already done, resume the backfiller. When the loop stops,
|
||||||
// terminate the backfiller too.
|
// terminate the backfiller too.
|
||||||
linked := len(s.progress.Subchains) == 1 &&
|
linked := len(s.progress.Subchains) == 1 && s.linked(s.scratchHead, s.progress.Subchains[0].Next)
|
||||||
rawdb.HasHeader(s.db, s.progress.Subchains[0].Next, s.scratchHead) &&
|
|
||||||
rawdb.HasBody(s.db, s.progress.Subchains[0].Next, s.scratchHead) &&
|
|
||||||
rawdb.HasReceipts(s.db, s.progress.Subchains[0].Next, s.scratchHead)
|
|
||||||
if linked {
|
if linked {
|
||||||
s.filler.resume()
|
s.filler.resume()
|
||||||
}
|
}
|
||||||
|
|
@ -497,12 +525,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) {
|
||||||
// is still running, it will pick it up. If it already terminated,
|
// is still running, it will pick it up. If it already terminated,
|
||||||
// a new cycle needs to be spun up.
|
// a new cycle needs to be spun up.
|
||||||
if linked {
|
if linked {
|
||||||
linked = len(s.progress.Subchains) == 1 &&
|
if len(s.progress.Subchains) == 1 && s.linked(s.scratchHead, s.progress.Subchains[0].Next) {
|
||||||
rawdb.HasHeader(s.db, s.progress.Subchains[0].Next, s.scratchHead) &&
|
|
||||||
rawdb.HasBody(s.db, s.progress.Subchains[0].Next, s.scratchHead) &&
|
|
||||||
rawdb.HasReceipts(s.db, s.progress.Subchains[0].Next, s.scratchHead)
|
|
||||||
|
|
||||||
if linked {
|
|
||||||
// The skeleton chain has been extended and is still linked with the local
|
// The skeleton chain has been extended and is still linked with the local
|
||||||
// chain, try to re-schedule the backfiller if it's already terminated.
|
// chain, try to re-schedule the backfiller if it's already terminated.
|
||||||
s.filler.resume()
|
s.filler.resume()
|
||||||
|
|
@ -946,6 +969,45 @@ func (s *skeleton) revertRequest(req *headerRequest) {
|
||||||
s.scratchOwners[(s.scratchHead-req.head)/requestHeaders] = ""
|
s.scratchOwners[(s.scratchHead-req.head)/requestHeaders] = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeSubchains is invoked once certain beacon headers have been persisted locally
|
||||||
|
// and the subchains should be merged in case there are some overlaps between. An
|
||||||
|
// indicator will be returned if the last subchain is merged with previous subchain.
|
||||||
|
func (s *skeleton) mergeSubchains() bool {
|
||||||
|
// If the subchain extended into the next subchain, we need to handle
|
||||||
|
// the overlap. Since there could be many overlaps, do this in a loop.
|
||||||
|
var merged bool
|
||||||
|
for len(s.progress.Subchains) > 1 && s.progress.Subchains[1].Head >= s.progress.Subchains[0].Tail {
|
||||||
|
// Extract some stats from the second subchain
|
||||||
|
head := s.progress.Subchains[1].Head
|
||||||
|
tail := s.progress.Subchains[1].Tail
|
||||||
|
next := s.progress.Subchains[1].Next
|
||||||
|
|
||||||
|
// Since we just overwrote part of the next subchain, we need to trim
|
||||||
|
// its head independent of matching or mismatching content
|
||||||
|
if s.progress.Subchains[1].Tail >= s.progress.Subchains[0].Tail {
|
||||||
|
// Fully overwritten, get rid of the subchain as a whole
|
||||||
|
log.Debug("Previous subchain fully overwritten", "head", head, "tail", tail, "next", next)
|
||||||
|
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// Partially overwritten, trim the head to the overwritten size
|
||||||
|
log.Debug("Previous subchain partially overwritten", "head", head, "tail", tail, "next", next)
|
||||||
|
s.progress.Subchains[1].Head = s.progress.Subchains[0].Tail - 1
|
||||||
|
}
|
||||||
|
// If the old subchain is an extension of the new one, merge the two
|
||||||
|
// and let the skeleton syncer restart (to clean internal state)
|
||||||
|
if rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[1].Head).Hash() == s.progress.Subchains[0].Next {
|
||||||
|
log.Debug("Previous subchain merged", "head", head, "tail", tail, "next", next)
|
||||||
|
s.progress.Subchains[0].Tail = s.progress.Subchains[1].Tail
|
||||||
|
s.progress.Subchains[0].Next = s.progress.Subchains[1].Next
|
||||||
|
|
||||||
|
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
|
||||||
|
merged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged bool) {
|
func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged bool) {
|
||||||
res.peer.log.Trace("Processing header response", "head", res.headers[0].Number, "hash", res.headers[0].Hash(), "count", len(res.headers))
|
res.peer.log.Trace("Processing header response", "head", res.headers[0].Number, "hash", res.headers[0].Hash(), "count", len(res.headers))
|
||||||
|
|
||||||
|
|
@ -1019,10 +1081,9 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
|
||||||
// processing is done, so it's just one more "needless" check.
|
// processing is done, so it's just one more "needless" check.
|
||||||
//
|
//
|
||||||
// The weird cascading checks are done to minimize the database reads.
|
// The weird cascading checks are done to minimize the database reads.
|
||||||
linked = rawdb.HasHeader(s.db, header.ParentHash, header.Number.Uint64()-1) &&
|
linked = s.linked(header.Number.Uint64()-1, header.ParentHash)
|
||||||
rawdb.HasBody(s.db, header.ParentHash, header.Number.Uint64()-1) &&
|
|
||||||
rawdb.HasReceipts(s.db, header.ParentHash, header.Number.Uint64()-1)
|
|
||||||
if linked {
|
if linked {
|
||||||
|
log.Debug("Primary subchain linked", "number", header.Number.Uint64()-1, "hash", header.ParentHash)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1036,6 +1097,9 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
|
||||||
// If the beacon chain was linked to the local chain, completely swap out
|
// If the beacon chain was linked to the local chain, completely swap out
|
||||||
// all internal progress and abort header synchronization.
|
// all internal progress and abort header synchronization.
|
||||||
if linked {
|
if linked {
|
||||||
|
// Merge all overlapped subchains beforehand
|
||||||
|
s.mergeSubchains()
|
||||||
|
|
||||||
// Linking into the local chain should also mean that there are no
|
// Linking into the local chain should also mean that there are no
|
||||||
// leftover subchains, but in the case of importing the blocks via
|
// leftover subchains, but in the case of importing the blocks via
|
||||||
// the engine API, we will not push the subchains forward. This will
|
// the engine API, we will not push the subchains forward. This will
|
||||||
|
|
@ -1093,41 +1157,10 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
|
||||||
|
|
||||||
s.scratchHead -= uint64(consumed)
|
s.scratchHead -= uint64(consumed)
|
||||||
|
|
||||||
// If the subchain extended into the next subchain, we need to handle
|
|
||||||
// the overlap. Since there could be many overlaps (come on), do this
|
|
||||||
// in a loop.
|
|
||||||
for len(s.progress.Subchains) > 1 && s.progress.Subchains[1].Head >= s.progress.Subchains[0].Tail {
|
|
||||||
// Extract some stats from the second subchain
|
|
||||||
head := s.progress.Subchains[1].Head
|
|
||||||
tail := s.progress.Subchains[1].Tail
|
|
||||||
next := s.progress.Subchains[1].Next
|
|
||||||
|
|
||||||
// Since we just overwrote part of the next subchain, we need to trim
|
|
||||||
// its head independent of matching or mismatching content
|
|
||||||
if s.progress.Subchains[1].Tail >= s.progress.Subchains[0].Tail {
|
|
||||||
// Fully overwritten, get rid of the subchain as a whole
|
|
||||||
log.Debug("Previous subchain fully overwritten", "head", head, "tail", tail, "next", next)
|
|
||||||
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
// Partially overwritten, trim the head to the overwritten size
|
|
||||||
log.Debug("Previous subchain partially overwritten", "head", head, "tail", tail, "next", next)
|
|
||||||
s.progress.Subchains[1].Head = s.progress.Subchains[0].Tail - 1
|
|
||||||
}
|
|
||||||
// If the old subchain is an extension of the new one, merge the two
|
|
||||||
// and let the skeleton syncer restart (to clean internal state)
|
|
||||||
if rawdb.ReadSkeletonHeader(s.db, s.progress.Subchains[1].Head).Hash() == s.progress.Subchains[0].Next {
|
|
||||||
log.Debug("Previous subchain merged", "head", head, "tail", tail, "next", next)
|
|
||||||
s.progress.Subchains[0].Tail = s.progress.Subchains[1].Tail
|
|
||||||
s.progress.Subchains[0].Next = s.progress.Subchains[1].Next
|
|
||||||
|
|
||||||
s.progress.Subchains = append(s.progress.Subchains[:1], s.progress.Subchains[2:]...)
|
|
||||||
merged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If subchains were merged, all further available headers in the scratch
|
// If subchains were merged, all further available headers in the scratch
|
||||||
// space are invalid since we skipped ahead. Stop processing the scratch
|
// space are invalid since we skipped ahead. Stop processing the scratch
|
||||||
// space to avoid dropping peers thinking they delivered invalid data.
|
// space to avoid dropping peers thinking they delivered invalid data.
|
||||||
|
merged = s.mergeSubchains()
|
||||||
if merged {
|
if merged {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -1158,15 +1191,17 @@ func (s *skeleton) processResponse(res *headerResponse) (linked bool, merged boo
|
||||||
// due to the downloader backfilling past the tracked tail.
|
// due to the downloader backfilling past the tracked tail.
|
||||||
func (s *skeleton) cleanStales(filled *types.Header) error {
|
func (s *skeleton) cleanStales(filled *types.Header) error {
|
||||||
number := filled.Number.Uint64()
|
number := filled.Number.Uint64()
|
||||||
log.Trace("Cleaning stale beacon headers", "filled", number, "hash", filled.Hash())
|
log.Debug("Cleaning stale beacon headers", "filled", number, "hash", filled.Hash())
|
||||||
|
|
||||||
// If the filled header is below the linked subchain, something's corrupted
|
// If the filled header is below the subchain, it means the skeleton is not
|
||||||
// internally. Report and error and refuse to do anything.
|
// linked with local chain yet, don't bother to do cleanup.
|
||||||
if number+1 < s.progress.Subchains[0].Tail {
|
if number+1 < s.progress.Subchains[0].Tail {
|
||||||
return fmt.Errorf("filled header below beacon header tail: %d < %d", number, s.progress.Subchains[0].Tail)
|
log.Debug("filled header below beacon header tail", "filled", number, "tail", s.progress.Subchains[0].Tail)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
// If nothing in subchain is filled, don't bother to do cleanup.
|
// If nothing in subchain is filled, don't bother to do cleanup.
|
||||||
if number+1 == s.progress.Subchains[0].Tail {
|
if number+1 == s.progress.Subchains[0].Tail {
|
||||||
|
log.Debug("Skeleton chain not yet consumed", "filled", number, "hash", filled.Hash(), "tail", s.progress.Subchains[0].Tail)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// If the latest fill was on a different subchain, it means the backfiller
|
// If the latest fill was on a different subchain, it means the backfiller
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -71,6 +72,12 @@ func (hf *hookedBackfiller) resume() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeChainReader struct{}
|
||||||
|
|
||||||
|
func (fc *fakeChainReader) CurrentSnapBlock() *types.Header {
|
||||||
|
return &types.Header{Number: big.NewInt(math.MaxInt64)}
|
||||||
|
}
|
||||||
|
|
||||||
// skeletonTestPeer is a mock peer that can only serve header requests from a
|
// skeletonTestPeer is a mock peer that can only serve header requests from a
|
||||||
// pre-perated header chain (which may be arbitrarily wrong for testing).
|
// pre-perated header chain (which may be arbitrarily wrong for testing).
|
||||||
//
|
//
|
||||||
|
|
@ -369,7 +376,7 @@ func TestSkeletonSyncInit(t *testing.T) {
|
||||||
// Create a skeleton sync and run a cycle
|
// Create a skeleton sync and run a cycle
|
||||||
wait := make(chan struct{})
|
wait := make(chan struct{})
|
||||||
|
|
||||||
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller())
|
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller(), &fakeChainReader{})
|
||||||
skeleton.syncStarting = func() { close(wait) }
|
skeleton.syncStarting = func() { close(wait) }
|
||||||
skeleton.Sync(tt.head, nil, true)
|
skeleton.Sync(tt.head, nil, true)
|
||||||
|
|
||||||
|
|
@ -472,7 +479,7 @@ func TestSkeletonSyncExtend(t *testing.T) {
|
||||||
// Create a skeleton sync and run a cycle
|
// Create a skeleton sync and run a cycle
|
||||||
wait := make(chan struct{})
|
wait := make(chan struct{})
|
||||||
|
|
||||||
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller())
|
skeleton := newSkeleton(db, newPeerSet(), nil, newHookedBackfiller(), &fakeChainReader{})
|
||||||
skeleton.syncStarting = func() { close(wait) }
|
skeleton.syncStarting = func() { close(wait) }
|
||||||
skeleton.Sync(tt.head, nil, true)
|
skeleton.Sync(tt.head, nil, true)
|
||||||
|
|
||||||
|
|
@ -885,7 +892,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create a skeleton sync and run a cycle
|
// Create a skeleton sync and run a cycle
|
||||||
skeleton := newSkeleton(db, peerset, drop, filler)
|
skeleton := newSkeleton(db, peerset, drop, filler, &fakeChainReader{})
|
||||||
skeleton.Sync(tt.head, nil, true)
|
skeleton.Sync(tt.head, nil, true)
|
||||||
|
|
||||||
// Wait a bit (bleah) for the initial sync loop to go to idle. This might
|
// Wait a bit (bleah) for the initial sync loop to go to idle. This might
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -15,7 +15,6 @@ require (
|
||||||
github.com/cockroachdb/pebble v1.1.5
|
github.com/cockroachdb/pebble v1.1.5
|
||||||
github.com/consensys/gnark-crypto v0.18.1
|
github.com/consensys/gnark-crypto v0.18.1
|
||||||
github.com/crate-crypto/go-eth-kzg v1.4.0
|
github.com/crate-crypto/go-eth-kzg v1.4.0
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a
|
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/dchest/siphash v1.2.3
|
github.com/dchest/siphash v1.2.3
|
||||||
github.com/deckarep/golang-set/v2 v2.6.0
|
github.com/deckarep/golang-set/v2 v2.6.0
|
||||||
|
|
@ -24,7 +23,6 @@ require (
|
||||||
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
|
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
|
||||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5
|
github.com/ethereum/c-kzg-4844/v2 v2.1.5
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab
|
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab
|
||||||
github.com/ethereum/go-verkle v0.2.2
|
|
||||||
github.com/fatih/color v1.16.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/ferranbt/fastssz v0.1.4
|
github.com/ferranbt/fastssz v0.1.4
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -81,8 +81,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
|
||||||
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
|
||||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
@ -117,8 +115,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3
|
||||||
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
|
||||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ const (
|
||||||
|
|
||||||
MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis.
|
MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis.
|
||||||
ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction.
|
ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction.
|
||||||
SloadGas uint64 = 50 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added.
|
SloadGas uint64 = 50 //
|
||||||
CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero.
|
CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero.
|
||||||
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
|
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
|
||||||
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
|
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
|
||||||
|
|
@ -82,7 +82,7 @@ const (
|
||||||
CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack.
|
CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack.
|
||||||
ExpGas uint64 = 10 // Once per EXP instruction
|
ExpGas uint64 = 10 // Once per EXP instruction
|
||||||
LogGas uint64 = 375 // Per LOG* operation.
|
LogGas uint64 = 375 // Per LOG* operation.
|
||||||
CopyGas uint64 = 3 //
|
CopyGas uint64 = 3 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added.
|
||||||
StackLimit uint64 = 1024 // Maximum size of VM stack allowed.
|
StackLimit uint64 = 1024 // Maximum size of VM stack allowed.
|
||||||
TierStepGas uint64 = 0 // Once per operation, for a selection of them.
|
TierStepGas uint64 = 0 // Once per operation, for a selection of them.
|
||||||
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
|
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,17 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
zeroHash = common.Hash{}
|
zeroInt = uint256.NewInt(0)
|
||||||
codeOffset = uint256.NewInt(128)
|
zeroHash = common.Hash{}
|
||||||
|
verkleNodeWidthLog2 = 8
|
||||||
|
headerStorageOffset = uint256.NewInt(64)
|
||||||
|
codeOffset = uint256.NewInt(128)
|
||||||
|
codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
|
||||||
|
mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(1), 248-uint(verkleNodeWidthLog2))
|
||||||
|
CodeOffset = uint256.NewInt(128)
|
||||||
|
VerkleNodeWidth = uint256.NewInt(256)
|
||||||
|
HeaderStorageOffset = uint256.NewInt(64)
|
||||||
|
VerkleNodeWidthLog2 = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
|
func GetBinaryTreeKey(addr common.Address, key []byte) []byte {
|
||||||
|
|
@ -83,3 +92,38 @@ func GetBinaryTreeKeyCodeChunk(address common.Address, chunknr *uint256.Int) []b
|
||||||
chunkOffset := new(uint256.Int).Add(codeOffset, chunknr).Bytes()
|
chunkOffset := new(uint256.Int).Add(codeOffset, chunknr).Bytes()
|
||||||
return GetBinaryTreeKey(address, chunkOffset)
|
return GetBinaryTreeKey(address, chunkOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StorageIndex(storageKey []byte) (*uint256.Int, byte) {
|
||||||
|
// If the storage slot is in the header, we need to add the header offset.
|
||||||
|
var key uint256.Int
|
||||||
|
key.SetBytes(storageKey)
|
||||||
|
if key.Cmp(codeStorageDelta) < 0 {
|
||||||
|
// This addition is always safe; it can't ever overflow since pos<codeStorageDelta.
|
||||||
|
key.Add(headerStorageOffset, &key)
|
||||||
|
|
||||||
|
// In this branch, the tree-index is zero since we're in the account header,
|
||||||
|
// and the sub-index is the LSB of the modified storage key.
|
||||||
|
return zeroInt, byte(key[0] & 0xFF)
|
||||||
|
}
|
||||||
|
// If the storage slot is in the main storage, we need to add the main storage offset.
|
||||||
|
|
||||||
|
// The first MAIN_STORAGE_OFFSET group will see its
|
||||||
|
// first 64 slots unreachable. This is either a typo in the
|
||||||
|
// spec or intended to conserve the 256-u256
|
||||||
|
// alignment. If we decide to ever access these 64
|
||||||
|
// slots, uncomment this.
|
||||||
|
// // Get the new offset since we now know that we are above 64.
|
||||||
|
// pos.Sub(&pos, codeStorageDelta)
|
||||||
|
// suffix := byte(pos[0] & 0xFF)
|
||||||
|
suffix := storageKey[len(storageKey)-1]
|
||||||
|
|
||||||
|
// We first divide by VerkleNodeWidth to create room to avoid an overflow next.
|
||||||
|
key.Rsh(&key, uint(verkleNodeWidthLog2))
|
||||||
|
|
||||||
|
// We add mainStorageOffset/VerkleNodeWidth which can't overflow.
|
||||||
|
key.Add(&key, mainStorageOffsetLshVerkleNodeWidth)
|
||||||
|
|
||||||
|
// The sub-index is the LSB of the original storage key, since mainStorageOffset
|
||||||
|
// doesn't affect this byte, so we can avoid masks or shifts.
|
||||||
|
return &key, suffix
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,10 +79,7 @@ func ChunkifyCode(code []byte) ChunkedCode {
|
||||||
chunks := make([]byte, chunkCount*HashSize)
|
chunks := make([]byte, chunkCount*HashSize)
|
||||||
for i := 0; i < chunkCount; i++ {
|
for i := 0; i < chunkCount; i++ {
|
||||||
// number of bytes to copy, StemSize unless the end of the code has been reached.
|
// number of bytes to copy, StemSize unless the end of the code has been reached.
|
||||||
end := StemSize * (i + 1)
|
end := min(len(code), StemSize*(i+1))
|
||||||
if len(code) < end {
|
|
||||||
end = len(code)
|
|
||||||
}
|
|
||||||
copy(chunks[i*HashSize+1:], code[StemSize*i:end]) // copy the code itself
|
copy(chunks[i*HashSize+1:], code[StemSize*i:end]) // copy the code itself
|
||||||
|
|
||||||
// chunk offset = taken from the last chunk.
|
// chunk offset = taken from the last chunk.
|
||||||
|
|
|
||||||
|
|
@ -1,413 +0,0 @@
|
||||||
// Copyright 2023 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/crate-crypto/go-ipa/bandersnatch/fr"
|
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
|
||||||
"github.com/ethereum/go-verkle"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
BasicDataLeafKey = 0
|
|
||||||
CodeHashLeafKey = 1
|
|
||||||
|
|
||||||
BasicDataVersionOffset = 0
|
|
||||||
BasicDataCodeSizeOffset = 5
|
|
||||||
BasicDataNonceOffset = 8
|
|
||||||
BasicDataBalanceOffset = 16
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
zero = uint256.NewInt(0)
|
|
||||||
verkleNodeWidthLog2 = 8
|
|
||||||
headerStorageOffset = uint256.NewInt(64)
|
|
||||||
codeOffset = uint256.NewInt(128)
|
|
||||||
verkleNodeWidth = uint256.NewInt(256)
|
|
||||||
codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset)
|
|
||||||
mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(1), 248-uint(verkleNodeWidthLog2))
|
|
||||||
CodeOffset = uint256.NewInt(128)
|
|
||||||
VerkleNodeWidth = uint256.NewInt(256)
|
|
||||||
HeaderStorageOffset = uint256.NewInt(64)
|
|
||||||
VerkleNodeWidthLog2 = 8
|
|
||||||
|
|
||||||
index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64]
|
|
||||||
|
|
||||||
// cacheHitGauge is the metric to track how many cache hit occurred.
|
|
||||||
cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil)
|
|
||||||
|
|
||||||
// cacheMissGauge is the metric to track how many cache miss occurred.
|
|
||||||
cacheMissGauge = metrics.NewRegisteredGauge("trie/verkle/cache/miss", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// The byte array is the Marshalled output of the point computed as such:
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// config = verkle.GetConfig()
|
|
||||||
// fr verkle.Fr
|
|
||||||
// )
|
|
||||||
// verkle.FromLEBytes(&fr, []byte{2, 64})
|
|
||||||
// point := config.CommitToPoly([]verkle.Fr{fr}, 1)
|
|
||||||
index0Point = new(verkle.Point)
|
|
||||||
err := index0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PointCache is the LRU cache for storing evaluated address commitment.
|
|
||||||
type PointCache struct {
|
|
||||||
lru lru.BasicLRU[string, *verkle.Point]
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPointCache returns the cache with specified size.
|
|
||||||
func NewPointCache(maxItems int) *PointCache {
|
|
||||||
return &PointCache{
|
|
||||||
lru: lru.NewBasicLRU[string, *verkle.Point](maxItems),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the cached commitment for the specified address, or computing
|
|
||||||
// it on the flight.
|
|
||||||
func (c *PointCache) Get(addr []byte) *verkle.Point {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
|
|
||||||
p, ok := c.lru.Get(string(addr))
|
|
||||||
if ok {
|
|
||||||
cacheHitGauge.Inc(1)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
cacheMissGauge.Inc(1)
|
|
||||||
p = evaluateAddressPoint(addr)
|
|
||||||
c.lru.Add(string(addr), p)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStem returns the first 31 bytes of the tree key as the tree stem. It only
|
|
||||||
// works for the account metadata whose treeIndex is 0.
|
|
||||||
func (c *PointCache) GetStem(addr []byte) []byte {
|
|
||||||
p := c.Get(addr)
|
|
||||||
return pointToHash(p, 0)[:31]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTreeKey performs both the work of the spec's get_tree_key function, and that
|
|
||||||
// of pedersen_hash: it builds the polynomial in pedersen_hash without having to
|
|
||||||
// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte
|
|
||||||
// array. Since at most the first 5 coefficients of the polynomial will be non-zero,
|
|
||||||
// these 5 coefficients are created directly.
|
|
||||||
func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte {
|
|
||||||
if len(address) < 32 {
|
|
||||||
var aligned [32]byte
|
|
||||||
address = append(aligned[:32-len(address)], address...)
|
|
||||||
}
|
|
||||||
// poly = [2+256*64, address_le_low, address_le_high, tree_index_le_low, tree_index_le_high]
|
|
||||||
var poly [5]fr.Element
|
|
||||||
|
|
||||||
// 32-byte address, interpreted as two little endian
|
|
||||||
// 16-byte numbers.
|
|
||||||
verkle.FromLEBytes(&poly[1], address[:16])
|
|
||||||
verkle.FromLEBytes(&poly[2], address[16:])
|
|
||||||
|
|
||||||
// treeIndex must be interpreted as a 32-byte aligned little-endian integer.
|
|
||||||
// e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00.
|
|
||||||
// poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes).
|
|
||||||
//
|
|
||||||
// To avoid unnecessary endianness conversions for go-ipa, we do some trick:
|
|
||||||
// - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of
|
|
||||||
// 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})).
|
|
||||||
// - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of
|
|
||||||
// the 32-byte aligned big-endian representation (BE({00,00,...}).
|
|
||||||
trieIndexBytes := treeIndex.Bytes32()
|
|
||||||
verkle.FromBytes(&poly[3], trieIndexBytes[16:])
|
|
||||||
verkle.FromBytes(&poly[4], trieIndexBytes[:16])
|
|
||||||
|
|
||||||
cfg := verkle.GetConfig()
|
|
||||||
ret := cfg.CommitToPoly(poly[:], 0)
|
|
||||||
|
|
||||||
// add a constant point corresponding to poly[0]=[2+256*64].
|
|
||||||
ret.Add(ret, index0Point)
|
|
||||||
|
|
||||||
return pointToHash(ret, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only
|
|
||||||
// difference is a part of polynomial is already evaluated.
|
|
||||||
//
|
|
||||||
// Specifically, poly = [2+256*64, address_le_low, address_le_high] is already
|
|
||||||
// evaluated.
|
|
||||||
func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte {
|
|
||||||
var poly [5]fr.Element
|
|
||||||
|
|
||||||
// little-endian, 32-byte aligned treeIndex
|
|
||||||
var index [32]byte
|
|
||||||
for i := 0; i < len(treeIndex); i++ {
|
|
||||||
binary.LittleEndian.PutUint64(index[i*8:(i+1)*8], treeIndex[i])
|
|
||||||
}
|
|
||||||
verkle.FromLEBytes(&poly[3], index[:16])
|
|
||||||
verkle.FromLEBytes(&poly[4], index[16:])
|
|
||||||
|
|
||||||
cfg := verkle.GetConfig()
|
|
||||||
ret := cfg.CommitToPoly(poly[:], 0)
|
|
||||||
|
|
||||||
// add the pre-evaluated address
|
|
||||||
ret.Add(ret, evaluated)
|
|
||||||
|
|
||||||
return pointToHash(ret, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicDataKey returns the verkle tree key of the basic data field for
|
|
||||||
// the specified account.
|
|
||||||
func BasicDataKey(address []byte) []byte {
|
|
||||||
return GetTreeKey(address, zero, BasicDataLeafKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeHashKey returns the verkle tree key of the code hash field for
|
|
||||||
// the specified account.
|
|
||||||
func CodeHashKey(address []byte) []byte {
|
|
||||||
return GetTreeKey(address, zero, CodeHashLeafKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) {
|
|
||||||
var (
|
|
||||||
chunkOffset = new(uint256.Int).Add(codeOffset, chunk)
|
|
||||||
treeIndex, subIndexMod = new(uint256.Int).DivMod(chunkOffset, verkleNodeWidth, new(uint256.Int))
|
|
||||||
)
|
|
||||||
return treeIndex, byte(subIndexMod.Uint64())
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeChunkKey returns the verkle tree key of the code chunk for the
|
|
||||||
// specified account.
|
|
||||||
func CodeChunkKey(address []byte, chunk *uint256.Int) []byte {
|
|
||||||
treeIndex, subIndex := codeChunkIndex(chunk)
|
|
||||||
return GetTreeKey(address, treeIndex, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTreeKeyCodeChunkIndices(chunk *uint256.Int) (*uint256.Int, byte) {
|
|
||||||
chunkOffset := new(uint256.Int).Add(CodeOffset, chunk)
|
|
||||||
treeIndex := new(uint256.Int).Div(chunkOffset, VerkleNodeWidth)
|
|
||||||
subIndexMod := new(uint256.Int).Mod(chunkOffset, VerkleNodeWidth)
|
|
||||||
var subIndex byte
|
|
||||||
if len(subIndexMod) != 0 {
|
|
||||||
subIndex = byte(subIndexMod[0])
|
|
||||||
}
|
|
||||||
return treeIndex, subIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTreeKeyCodeChunk(address []byte, chunk *uint256.Int) []byte {
|
|
||||||
treeIndex, subIndex := GetTreeKeyCodeChunkIndices(chunk)
|
|
||||||
return GetTreeKey(address, treeIndex, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageIndex(storageKey []byte) (*uint256.Int, byte) {
|
|
||||||
// If the storage slot is in the header, we need to add the header offset.
|
|
||||||
var key uint256.Int
|
|
||||||
key.SetBytes(storageKey)
|
|
||||||
if key.Cmp(codeStorageDelta) < 0 {
|
|
||||||
// This addition is always safe; it can't ever overflow since pos<codeStorageDelta.
|
|
||||||
key.Add(headerStorageOffset, &key)
|
|
||||||
|
|
||||||
// In this branch, the tree-index is zero since we're in the account header,
|
|
||||||
// and the sub-index is the LSB of the modified storage key.
|
|
||||||
return zero, byte(key[0] & 0xFF)
|
|
||||||
}
|
|
||||||
// If the storage slot is in the main storage, we need to add the main storage offset.
|
|
||||||
|
|
||||||
// The first MAIN_STORAGE_OFFSET group will see its
|
|
||||||
// first 64 slots unreachable. This is either a typo in the
|
|
||||||
// spec or intended to conserve the 256-u256
|
|
||||||
// alignment. If we decide to ever access these 64
|
|
||||||
// slots, uncomment this.
|
|
||||||
// // Get the new offset since we now know that we are above 64.
|
|
||||||
// pos.Sub(&pos, codeStorageDelta)
|
|
||||||
// suffix := byte(pos[0] & 0xFF)
|
|
||||||
suffix := storageKey[len(storageKey)-1]
|
|
||||||
|
|
||||||
// We first divide by VerkleNodeWidth to create room to avoid an overflow next.
|
|
||||||
key.Rsh(&key, uint(verkleNodeWidthLog2))
|
|
||||||
|
|
||||||
// We add mainStorageOffset/VerkleNodeWidth which can't overflow.
|
|
||||||
key.Add(&key, mainStorageOffsetLshVerkleNodeWidth)
|
|
||||||
|
|
||||||
// The sub-index is the LSB of the original storage key, since mainStorageOffset
|
|
||||||
// doesn't affect this byte, so we can avoid masks or shifts.
|
|
||||||
return &key, suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageSlotKey returns the verkle tree key of the storage slot for the
|
|
||||||
// specified account.
|
|
||||||
func StorageSlotKey(address []byte, storageKey []byte) []byte {
|
|
||||||
treeIndex, subIndex := StorageIndex(storageKey)
|
|
||||||
return GetTreeKey(address, treeIndex, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicDataKeyWithEvaluatedAddress returns the verkle tree key of the basic data
|
|
||||||
// field for the specified account. The difference between BasicDataKey is the
|
|
||||||
// address evaluation is already computed to minimize the computational overhead.
|
|
||||||
func BasicDataKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
|
|
||||||
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, BasicDataLeafKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeHashKeyWithEvaluatedAddress returns the verkle tree key of the code
|
|
||||||
// hash for the specified account. The difference between CodeHashKey is the
|
|
||||||
// address evaluation is already computed to minimize the computational overhead.
|
|
||||||
func CodeHashKeyWithEvaluatedAddress(evaluated *verkle.Point) []byte {
|
|
||||||
return GetTreeKeyWithEvaluatedAddress(evaluated, zero, CodeHashLeafKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CodeChunkKeyWithEvaluatedAddress returns the verkle tree key of the code
|
|
||||||
// chunk for the specified account. The difference between CodeChunkKey is the
|
|
||||||
// address evaluation is already computed to minimize the computational overhead.
|
|
||||||
func CodeChunkKeyWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256.Int) []byte {
|
|
||||||
treeIndex, subIndex := codeChunkIndex(chunk)
|
|
||||||
return GetTreeKeyWithEvaluatedAddress(addressPoint, treeIndex, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageSlotKeyWithEvaluatedAddress returns the verkle tree key of the storage
|
|
||||||
// slot for the specified account. The difference between StorageSlotKey is the
|
|
||||||
// address evaluation is already computed to minimize the computational overhead.
|
|
||||||
func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte {
|
|
||||||
treeIndex, subIndex := StorageIndex(storageKey)
|
|
||||||
return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pointToHash(evaluated *verkle.Point, suffix byte) []byte {
|
|
||||||
retb := verkle.HashPointToBytes(evaluated)
|
|
||||||
retb[31] = suffix
|
|
||||||
return retb[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func evaluateAddressPoint(address []byte) *verkle.Point {
|
|
||||||
if len(address) < 32 {
|
|
||||||
var aligned [32]byte
|
|
||||||
address = append(aligned[:32-len(address)], address...)
|
|
||||||
}
|
|
||||||
var poly [3]fr.Element
|
|
||||||
|
|
||||||
// 32-byte address, interpreted as two little endian
|
|
||||||
// 16-byte numbers.
|
|
||||||
verkle.FromLEBytes(&poly[1], address[:16])
|
|
||||||
verkle.FromLEBytes(&poly[2], address[16:])
|
|
||||||
|
|
||||||
cfg := verkle.GetConfig()
|
|
||||||
ret := cfg.CommitToPoly(poly[:], 0)
|
|
||||||
|
|
||||||
// add a constant point
|
|
||||||
ret.Add(ret, index0Point)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func EvaluateAddressPoint(address []byte) *verkle.Point {
|
|
||||||
if len(address) < 32 {
|
|
||||||
var aligned [32]byte
|
|
||||||
address = append(aligned[:32-len(address)], address...)
|
|
||||||
}
|
|
||||||
var poly [3]fr.Element
|
|
||||||
|
|
||||||
poly[0].SetZero()
|
|
||||||
|
|
||||||
// 32-byte address, interpreted as two little endian
|
|
||||||
// 16-byte numbers.
|
|
||||||
verkle.FromLEBytes(&poly[1], address[:16])
|
|
||||||
verkle.FromLEBytes(&poly[2], address[16:])
|
|
||||||
|
|
||||||
cfg := verkle.GetConfig()
|
|
||||||
ret := cfg.CommitToPoly(poly[:], 0)
|
|
||||||
|
|
||||||
// add a constant point
|
|
||||||
ret.Add(ret, index0Point)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTreeKeyStorageSlotWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte {
|
|
||||||
treeIndex, subIndex := GetTreeKeyStorageSlotTreeIndexes(storageKey)
|
|
||||||
return GetTreeKeyWithEvaluatedAddess(evaluated, treeIndex, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTreeKeyStorageSlotTreeIndexes(storageKey []byte) (*uint256.Int, byte) {
|
|
||||||
var pos uint256.Int
|
|
||||||
pos.SetBytes(storageKey)
|
|
||||||
|
|
||||||
// If the storage slot is in the header, we need to add the header offset.
|
|
||||||
if pos.Cmp(codeStorageDelta) < 0 {
|
|
||||||
// This addition is always safe; it can't ever overflow since pos<codeStorageDelta.
|
|
||||||
pos.Add(HeaderStorageOffset, &pos)
|
|
||||||
|
|
||||||
// In this branch, the tree-index is zero since we're in the account header,
|
|
||||||
// and the sub-index is the LSB of the modified storage key.
|
|
||||||
return zero, byte(pos[0] & 0xFF)
|
|
||||||
}
|
|
||||||
// If the storage slot is in the main storage, we need to add the main storage offset.
|
|
||||||
|
|
||||||
// The first MAIN_STORAGE_OFFSET group will see its
|
|
||||||
// first 64 slots unreachable. This is either a typo in the
|
|
||||||
// spec or intended to conserve the 256-u256
|
|
||||||
// aligment. If we decide to ever access these 64
|
|
||||||
// slots, uncomment this.
|
|
||||||
// // Get the new offset since we now know that we are above 64.
|
|
||||||
// pos.Sub(&pos, codeStorageDelta)
|
|
||||||
// suffix := byte(pos[0] & 0xFF)
|
|
||||||
suffix := storageKey[len(storageKey)-1]
|
|
||||||
|
|
||||||
// We first divide by VerkleNodeWidth to create room to avoid an overflow next.
|
|
||||||
pos.Rsh(&pos, uint(VerkleNodeWidthLog2))
|
|
||||||
|
|
||||||
// We add mainStorageOffset/VerkleNodeWidth which can't overflow.
|
|
||||||
pos.Add(&pos, mainStorageOffsetLshVerkleNodeWidth)
|
|
||||||
|
|
||||||
// The sub-index is the LSB of the original storage key, since mainStorageOffset
|
|
||||||
// doesn't affect this byte, so we can avoid masks or shifts.
|
|
||||||
return &pos, suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTreeKeyWithEvaluatedAddess(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte {
|
|
||||||
var poly [5]fr.Element
|
|
||||||
|
|
||||||
poly[0].SetZero()
|
|
||||||
poly[1].SetZero()
|
|
||||||
poly[2].SetZero()
|
|
||||||
|
|
||||||
trieIndexBytes := treeIndex.Bytes32()
|
|
||||||
verkle.FromBytes(&poly[3], trieIndexBytes[16:])
|
|
||||||
verkle.FromBytes(&poly[4], trieIndexBytes[:16])
|
|
||||||
|
|
||||||
cfg := verkle.GetConfig()
|
|
||||||
ret := cfg.CommitToPoly(poly[:], 0)
|
|
||||||
|
|
||||||
// add the pre-evaluated address
|
|
||||||
ret.Add(ret, evaluated)
|
|
||||||
|
|
||||||
return PointToHash(ret, subIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTreeKeyBasicDataEvaluatedAddress(addrp *verkle.Point) []byte {
|
|
||||||
return GetTreeKeyWithEvaluatedAddess(addrp, zero, BasicDataLeafKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PointToHash(evaluated *verkle.Point, suffix byte) []byte {
|
|
||||||
retb := verkle.HashPointToBytes(evaluated)
|
|
||||||
retb[31] = suffix
|
|
||||||
return retb[:]
|
|
||||||
}
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
// Copyright 2023 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-verkle"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTreeKey(t *testing.T) {
|
|
||||||
var (
|
|
||||||
address = []byte{0x01}
|
|
||||||
addressEval = evaluateAddressPoint(address)
|
|
||||||
smallIndex = uint256.NewInt(1)
|
|
||||||
largeIndex = uint256.NewInt(10000)
|
|
||||||
smallStorage = []byte{0x1}
|
|
||||||
largeStorage = bytes.Repeat([]byte{0xff}, 16)
|
|
||||||
)
|
|
||||||
if !bytes.Equal(BasicDataKey(address), BasicDataKeyWithEvaluatedAddress(addressEval)) {
|
|
||||||
t.Fatal("Unmatched basic data key")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(CodeHashKey(address), CodeHashKeyWithEvaluatedAddress(addressEval)) {
|
|
||||||
t.Fatal("Unmatched code hash key")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) {
|
|
||||||
t.Fatal("Unmatched code chunk key")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(CodeChunkKey(address, largeIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, largeIndex)) {
|
|
||||||
t.Fatal("Unmatched code chunk key")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(StorageSlotKey(address, smallStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, smallStorage)) {
|
|
||||||
t.Fatal("Unmatched storage slot key")
|
|
||||||
}
|
|
||||||
if !bytes.Equal(StorageSlotKey(address, largeStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, largeStorage)) {
|
|
||||||
t.Fatal("Unmatched storage slot key")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goos: darwin
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/ethereum/go-ethereum/trie/utils
|
|
||||||
// cpu: VirtualApple @ 2.50GHz
|
|
||||||
// BenchmarkTreeKey
|
|
||||||
// BenchmarkTreeKey-8 398731 2961 ns/op 32 B/op 1 allocs/op
|
|
||||||
func BenchmarkTreeKey(b *testing.B) {
|
|
||||||
// Initialize the IPA settings which can be pretty expensive.
|
|
||||||
verkle.GetConfig()
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
BasicDataKey([]byte{0x01})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goos: darwin
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/ethereum/go-ethereum/trie/utils
|
|
||||||
// cpu: VirtualApple @ 2.50GHz
|
|
||||||
// BenchmarkTreeKeyWithEvaluation
|
|
||||||
// BenchmarkTreeKeyWithEvaluation-8 513855 2324 ns/op 32 B/op 1 allocs/op
|
|
||||||
func BenchmarkTreeKeyWithEvaluation(b *testing.B) {
|
|
||||||
// Initialize the IPA settings which can be pretty expensive.
|
|
||||||
verkle.GetConfig()
|
|
||||||
|
|
||||||
addr := []byte{0x01}
|
|
||||||
eval := evaluateAddressPoint(addr)
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
BasicDataKeyWithEvaluatedAddress(eval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goos: darwin
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/ethereum/go-ethereum/trie/utils
|
|
||||||
// cpu: VirtualApple @ 2.50GHz
|
|
||||||
// BenchmarkStorageKey
|
|
||||||
// BenchmarkStorageKey-8 230516 4584 ns/op 96 B/op 3 allocs/op
|
|
||||||
func BenchmarkStorageKey(b *testing.B) {
|
|
||||||
// Initialize the IPA settings which can be pretty expensive.
|
|
||||||
verkle.GetConfig()
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
StorageSlotKey([]byte{0x01}, bytes.Repeat([]byte{0xff}, 32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goos: darwin
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/ethereum/go-ethereum/trie/utils
|
|
||||||
// cpu: VirtualApple @ 2.50GHz
|
|
||||||
// BenchmarkStorageKeyWithEvaluation
|
|
||||||
// BenchmarkStorageKeyWithEvaluation-8 320125 3753 ns/op 96 B/op 3 allocs/op
|
|
||||||
func BenchmarkStorageKeyWithEvaluation(b *testing.B) {
|
|
||||||
// Initialize the IPA settings which can be pretty expensive.
|
|
||||||
verkle.GetConfig()
|
|
||||||
|
|
||||||
addr := []byte{0x01}
|
|
||||||
eval := evaluateAddressPoint(addr)
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
StorageSlotKeyWithEvaluatedAddress(eval, bytes.Repeat([]byte{0xff}, 32))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
458
trie/verkle.go
458
trie/verkle.go
|
|
@ -1,458 +0,0 @@
|
||||||
// Copyright 2023 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package trie
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb/database"
|
|
||||||
"github.com/ethereum/go-verkle"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errInvalidRootType = errors.New("invalid node type for root")
|
|
||||||
)
|
|
||||||
|
|
||||||
// VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie
|
|
||||||
// interface so that Verkle trees can be reused verbatim.
|
|
||||||
type VerkleTrie struct {
|
|
||||||
root verkle.VerkleNode
|
|
||||||
cache *utils.PointCache
|
|
||||||
reader *Reader
|
|
||||||
tracer *PrevalueTracer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVerkleTrie constructs a verkle tree based on the specified root hash.
|
|
||||||
func NewVerkleTrie(root common.Hash, db database.NodeDatabase, cache *utils.PointCache) (*VerkleTrie, error) {
|
|
||||||
reader, err := NewReader(root, common.Hash{}, db)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t := &VerkleTrie{
|
|
||||||
root: verkle.New(),
|
|
||||||
cache: cache,
|
|
||||||
reader: reader,
|
|
||||||
tracer: NewPrevalueTracer(),
|
|
||||||
}
|
|
||||||
// Parse the root verkle node if it's not empty.
|
|
||||||
if root != types.EmptyVerkleHash && root != types.EmptyRootHash {
|
|
||||||
blob, err := t.nodeResolver(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
node, err := verkle.ParseNode(blob, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.root = node
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKey returns the sha3 preimage of a hashed key that was previously used
|
|
||||||
// to store a value.
|
|
||||||
func (t *VerkleTrie) GetKey(key []byte) []byte {
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccount implements state.Trie, retrieving the account with the specified
|
|
||||||
// account address. If the specified account is not in the verkle tree, nil will
|
|
||||||
// be returned. If the tree is corrupted, an error will be returned.
|
|
||||||
func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) {
|
|
||||||
var (
|
|
||||||
acc = &types.StateAccount{}
|
|
||||||
values [][]byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
switch n := t.root.(type) {
|
|
||||||
case *verkle.InternalNode:
|
|
||||||
values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errInvalidRootType
|
|
||||||
}
|
|
||||||
if values == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
basicData := values[utils.BasicDataLeafKey]
|
|
||||||
acc.Nonce = binary.BigEndian.Uint64(basicData[utils.BasicDataNonceOffset:])
|
|
||||||
acc.Balance = new(uint256.Int).SetBytes(basicData[utils.BasicDataBalanceOffset : utils.BasicDataBalanceOffset+16])
|
|
||||||
acc.CodeHash = values[utils.CodeHashLeafKey]
|
|
||||||
|
|
||||||
// TODO account.Root is leave as empty. How should we handle the legacy account?
|
|
||||||
return acc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefetchAccount attempts to resolve specific accounts from the database
|
|
||||||
// to accelerate subsequent trie operations.
|
|
||||||
func (t *VerkleTrie) PrefetchAccount(addresses []common.Address) error {
|
|
||||||
for _, addr := range addresses {
|
|
||||||
if _, err := t.GetAccount(addr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStorage implements state.Trie, retrieving the storage slot with the specified
|
|
||||||
// account address and storage key. If the specified slot is not in the verkle tree,
|
|
||||||
// nil will be returned. If the tree is corrupted, an error will be returned.
|
|
||||||
func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
|
|
||||||
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
|
|
||||||
val, err := t.root.Get(k, t.nodeResolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return common.TrimLeftZeroes(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefetchStorage attempts to resolve specific storage slots from the database
|
|
||||||
// to accelerate subsequent trie operations.
|
|
||||||
func (t *VerkleTrie) PrefetchStorage(addr common.Address, keys [][]byte) error {
|
|
||||||
for _, key := range keys {
|
|
||||||
if _, err := t.GetStorage(addr, key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateAccount implements state.Trie, writing the provided account into the tree.
|
|
||||||
// If the tree is corrupted, an error will be returned.
|
|
||||||
func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
basicData [32]byte
|
|
||||||
values = make([][]byte, verkle.NodeWidth)
|
|
||||||
stem = t.cache.GetStem(addr[:])
|
|
||||||
)
|
|
||||||
|
|
||||||
// Code size is encoded in BasicData as a 3-byte big-endian integer. Spare bytes are present
|
|
||||||
// before the code size to support bigger integers in the future. PutUint32(...) requires
|
|
||||||
// 4 bytes, so we need to shift the offset 1 byte to the left.
|
|
||||||
binary.BigEndian.PutUint32(basicData[utils.BasicDataCodeSizeOffset-1:], uint32(codeLen))
|
|
||||||
binary.BigEndian.PutUint64(basicData[utils.BasicDataNonceOffset:], acc.Nonce)
|
|
||||||
if acc.Balance.ByteLen() > 16 {
|
|
||||||
panic("balance too large")
|
|
||||||
}
|
|
||||||
acc.Balance.WriteToSlice(basicData[utils.BasicDataBalanceOffset : utils.BasicDataBalanceOffset+16])
|
|
||||||
values[utils.BasicDataLeafKey] = basicData[:]
|
|
||||||
values[utils.CodeHashLeafKey] = acc.CodeHash[:]
|
|
||||||
|
|
||||||
switch root := t.root.(type) {
|
|
||||||
case *verkle.InternalNode:
|
|
||||||
err = root.InsertValuesAtStem(stem, values, t.nodeResolver)
|
|
||||||
default:
|
|
||||||
return errInvalidRootType
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateStorage implements state.Trie, writing the provided storage slot into
|
|
||||||
// the tree. If the tree is corrupted, an error will be returned.
|
|
||||||
func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error {
|
|
||||||
// Left padding the slot value to 32 bytes.
|
|
||||||
var v [32]byte
|
|
||||||
if len(value) >= 32 {
|
|
||||||
copy(v[:], value[:32])
|
|
||||||
} else {
|
|
||||||
copy(v[32-len(value):], value[:])
|
|
||||||
}
|
|
||||||
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(address.Bytes()), key)
|
|
||||||
return t.root.Insert(k, v[:], t.nodeResolver)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// RollBackAccount removes the account info + code from the tree, unlike DeleteAccount
|
|
||||||
// that will overwrite it with 0s. The first 64 storage slots are also removed.
|
|
||||||
func (t *VerkleTrie) RollBackAccount(addr common.Address) error {
|
|
||||||
var (
|
|
||||||
evaluatedAddr = t.cache.Get(addr.Bytes())
|
|
||||||
basicDataKey = utils.BasicDataKeyWithEvaluatedAddress(evaluatedAddr)
|
|
||||||
)
|
|
||||||
basicDataBytes, err := t.root.Get(basicDataKey, t.nodeResolver)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("rollback: error finding code size: %w", err)
|
|
||||||
}
|
|
||||||
if len(basicDataBytes) == 0 {
|
|
||||||
return errors.New("rollback: basic data is not existent")
|
|
||||||
}
|
|
||||||
// The code size is encoded in BasicData as a 3-byte big-endian integer. Spare bytes are present
|
|
||||||
// before the code size to support bigger integers in the future.
|
|
||||||
// LittleEndian.Uint32(...) expects 4-bytes, so we need to shift the offset 1-byte to the left.
|
|
||||||
codeSize := binary.BigEndian.Uint32(basicDataBytes[utils.BasicDataCodeSizeOffset-1:])
|
|
||||||
|
|
||||||
// Delete the account header + first 64 slots + first 128 code chunks
|
|
||||||
_, err = t.root.(*verkle.InternalNode).DeleteAtStem(basicDataKey[:31], t.nodeResolver)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error rolling back account header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all further code
|
|
||||||
for i, chunknr := uint64(31*128), uint64(128); i < uint64(codeSize); i, chunknr = i+31*256, chunknr+256 {
|
|
||||||
// evaluate group key at the start of a new group
|
|
||||||
offset := uint256.NewInt(chunknr)
|
|
||||||
key := utils.CodeChunkKeyWithEvaluatedAddress(evaluatedAddr, offset)
|
|
||||||
|
|
||||||
if _, err = t.root.(*verkle.InternalNode).DeleteAtStem(key[:], t.nodeResolver); err != nil {
|
|
||||||
return fmt.Errorf("error deleting code chunk stem (addr=%x, offset=%d) error: %w", addr[:], offset, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteStorage implements state.Trie, deleting the specified storage slot from
|
|
||||||
// the trie. If the storage slot was not existent in the trie, no error will be
|
|
||||||
// returned. If the trie is corrupted, an error will be returned.
|
|
||||||
func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error {
|
|
||||||
var zero [32]byte
|
|
||||||
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
|
|
||||||
return t.root.Insert(k, zero[:], t.nodeResolver)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns the root hash of the tree. It does not write to the database and
|
|
||||||
// can be used even if the tree doesn't have one.
|
|
||||||
func (t *VerkleTrie) Hash() common.Hash {
|
|
||||||
return t.root.Commit().Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit writes all nodes to the tree's memory database.
|
|
||||||
func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
|
||||||
root := t.root.(*verkle.InternalNode)
|
|
||||||
nodes, err := root.BatchSerialize()
|
|
||||||
if err != nil {
|
|
||||||
// Error return from this function indicates error in the code logic
|
|
||||||
// of BatchSerialize, and we fail catastrophically if this is the case.
|
|
||||||
panic(fmt.Errorf("BatchSerialize failed: %v", err))
|
|
||||||
}
|
|
||||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
|
||||||
for _, node := range nodes {
|
|
||||||
// Hash parameter is not used in pathdb
|
|
||||||
nodeset.AddNode(node.Path, trienode.NewNodeWithPrev(common.Hash{}, node.SerializedBytes, t.tracer.Get(node.Path)))
|
|
||||||
}
|
|
||||||
// Serialize root commitment form
|
|
||||||
return t.Hash(), nodeset
|
|
||||||
}
|
|
||||||
|
|
||||||
// NodeIterator implements state.Trie, returning an iterator that returns
|
|
||||||
// nodes of the trie. Iteration starts at the key after the given start key.
|
|
||||||
//
|
|
||||||
// TODO(gballet, rjl493456442) implement it.
|
|
||||||
func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
|
|
||||||
// TODO(@CPerezz): remove.
|
|
||||||
return nil, errors.New("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prove implements state.Trie, constructing a Merkle proof for key. The result
|
|
||||||
// contains all encoded nodes on the path to the value at key. The value itself
|
|
||||||
// is also included in the last node and can be retrieved by verifying the proof.
|
|
||||||
//
|
|
||||||
// If the trie does not contain a value for key, the returned proof contains all
|
|
||||||
// nodes of the longest existing prefix of the key (at least the root), ending
|
|
||||||
// with the node that proves the absence of the key.
|
|
||||||
//
|
|
||||||
// TODO(gballet, rjl493456442) implement it.
|
|
||||||
func (t *VerkleTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns a deep-copied verkle tree.
|
|
||||||
func (t *VerkleTrie) Copy() *VerkleTrie {
|
|
||||||
return &VerkleTrie{
|
|
||||||
root: t.root.Copy(),
|
|
||||||
cache: t.cache,
|
|
||||||
reader: t.reader,
|
|
||||||
tracer: t.tracer.Copy(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsVerkle indicates if the trie is a Verkle trie.
|
|
||||||
func (t *VerkleTrie) IsVerkle() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proof builds and returns the verkle multiproof for keys, built against
|
|
||||||
// the pre tree. The post tree is passed in order to add the post values
|
|
||||||
// to that proof.
|
|
||||||
func (t *VerkleTrie) Proof(posttrie *VerkleTrie, keys [][]byte) (*verkle.VerkleProof, verkle.StateDiff, error) {
|
|
||||||
var postroot verkle.VerkleNode
|
|
||||||
if posttrie != nil {
|
|
||||||
postroot = posttrie.root
|
|
||||||
}
|
|
||||||
proof, _, _, _, err := verkle.MakeVerkleMultiProof(t.root, postroot, keys, t.nodeResolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
p, kvps, err := verkle.SerializeProof(proof)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return p, kvps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which
|
|
||||||
// are actual code, and 1 byte is the pushdata offset).
|
|
||||||
type ChunkedCode []byte
|
|
||||||
|
|
||||||
// Copy the values here so as to avoid an import cycle
|
|
||||||
const (
|
|
||||||
PUSH1 = byte(0x60)
|
|
||||||
PUSH32 = byte(0x7f)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChunkifyCode generates the chunked version of an array representing EVM bytecode
|
|
||||||
func ChunkifyCode(code []byte) ChunkedCode {
|
|
||||||
var (
|
|
||||||
chunkOffset = 0 // offset in the chunk
|
|
||||||
chunkCount = len(code) / 31
|
|
||||||
codeOffset = 0 // offset in the code
|
|
||||||
)
|
|
||||||
if len(code)%31 != 0 {
|
|
||||||
chunkCount++
|
|
||||||
}
|
|
||||||
chunks := make([]byte, chunkCount*32)
|
|
||||||
for i := 0; i < chunkCount; i++ {
|
|
||||||
// number of bytes to copy, 31 unless the end of the code has been reached.
|
|
||||||
end := 31 * (i + 1)
|
|
||||||
if len(code) < end {
|
|
||||||
end = len(code)
|
|
||||||
}
|
|
||||||
copy(chunks[i*32+1:], code[31*i:end]) // copy the code itself
|
|
||||||
|
|
||||||
// chunk offset = taken from the last chunk.
|
|
||||||
if chunkOffset > 31 {
|
|
||||||
// skip offset calculation if push data covers the whole chunk
|
|
||||||
chunks[i*32] = 31
|
|
||||||
chunkOffset = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
chunks[32*i] = byte(chunkOffset)
|
|
||||||
chunkOffset = 0
|
|
||||||
|
|
||||||
// Check each instruction and update the offset it should be 0 unless
|
|
||||||
// a PUSH-N overflows.
|
|
||||||
for ; codeOffset < end; codeOffset++ {
|
|
||||||
if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 {
|
|
||||||
codeOffset += int(code[codeOffset] - PUSH1 + 1)
|
|
||||||
if codeOffset+1 >= 31*(i+1) {
|
|
||||||
codeOffset++
|
|
||||||
chunkOffset = codeOffset - 31*(i+1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chunks
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateContractCode implements state.Trie, writing the provided contract code
|
|
||||||
// into the trie.
|
|
||||||
// Note that the code-size *must* be already saved by a previous UpdateAccount call.
|
|
||||||
func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
|
|
||||||
var (
|
|
||||||
chunks = ChunkifyCode(code)
|
|
||||||
values [][]byte
|
|
||||||
key []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 {
|
|
||||||
groupOffset := (chunknr + 128) % 256
|
|
||||||
if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ {
|
|
||||||
values = make([][]byte, verkle.NodeWidth)
|
|
||||||
key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr))
|
|
||||||
}
|
|
||||||
values[groupOffset] = chunks[i : i+32]
|
|
||||||
|
|
||||||
if groupOffset == 255 || len(chunks)-i <= 32 {
|
|
||||||
switch root := t.root.(type) {
|
|
||||||
case *verkle.InternalNode:
|
|
||||||
err = root.InsertValuesAtStem(key[:31], values, t.nodeResolver)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errInvalidRootType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *VerkleTrie) ToDot() string {
|
|
||||||
return verkle.ToDot(t.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
|
|
||||||
blob, err := t.reader.Node(path, common.Hash{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.tracer.Put(path, blob)
|
|
||||||
return blob, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Witness returns a set containing all trie nodes that have been accessed.
|
|
||||||
func (t *VerkleTrie) Witness() map[string][]byte {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
// Copyright 2023 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package trie
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/utils"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
accounts = map[common.Address]*types.StateAccount{
|
|
||||||
{1}: {
|
|
||||||
Nonce: 100,
|
|
||||||
Balance: uint256.NewInt(100),
|
|
||||||
CodeHash: common.Hash{0x1}.Bytes(),
|
|
||||||
},
|
|
||||||
{2}: {
|
|
||||||
Nonce: 200,
|
|
||||||
Balance: uint256.NewInt(200),
|
|
||||||
CodeHash: common.Hash{0x2}.Bytes(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
storages = map[common.Address]map[common.Hash][]byte{
|
|
||||||
{1}: {
|
|
||||||
common.Hash{10}: []byte{10},
|
|
||||||
common.Hash{11}: []byte{11},
|
|
||||||
common.MaxHash: []byte{0xff},
|
|
||||||
},
|
|
||||||
{2}: {
|
|
||||||
common.Hash{20}: []byte{20},
|
|
||||||
common.Hash{21}: []byte{21},
|
|
||||||
common.MaxHash: []byte{0xff},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVerkleTreeReadWrite(t *testing.T) {
|
|
||||||
db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
|
|
||||||
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
|
|
||||||
|
|
||||||
for addr, acct := range accounts {
|
|
||||||
if err := tr.UpdateAccount(addr, acct, 0); err != nil {
|
|
||||||
t.Fatalf("Failed to update account, %v", err)
|
|
||||||
}
|
|
||||||
for key, val := range storages[addr] {
|
|
||||||
if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil {
|
|
||||||
t.Fatalf("Failed to update storage, %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for addr, acct := range accounts {
|
|
||||||
stored, err := tr.GetAccount(addr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get account, %v", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(stored, acct) {
|
|
||||||
t.Fatal("account is not matched")
|
|
||||||
}
|
|
||||||
for key, val := range storages[addr] {
|
|
||||||
stored, err := tr.GetStorage(addr, key.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get storage, %v", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(stored, val) {
|
|
||||||
t.Fatal("storage is not matched")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerkleRollBack(t *testing.T) {
|
|
||||||
db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme)
|
|
||||||
tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100))
|
|
||||||
|
|
||||||
for addr, acct := range accounts {
|
|
||||||
// create more than 128 chunks of code
|
|
||||||
code := make([]byte, 129*32)
|
|
||||||
for i := 0; i < len(code); i += 2 {
|
|
||||||
code[i] = 0x60
|
|
||||||
code[i+1] = byte(i % 256)
|
|
||||||
}
|
|
||||||
if err := tr.UpdateAccount(addr, acct, len(code)); err != nil {
|
|
||||||
t.Fatalf("Failed to update account, %v", err)
|
|
||||||
}
|
|
||||||
for key, val := range storages[addr] {
|
|
||||||
if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil {
|
|
||||||
t.Fatalf("Failed to update storage, %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash := crypto.Keccak256Hash(code)
|
|
||||||
if err := tr.UpdateContractCode(addr, hash, code); err != nil {
|
|
||||||
t.Fatalf("Failed to update contract, %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that things were created
|
|
||||||
for addr, acct := range accounts {
|
|
||||||
stored, err := tr.GetAccount(addr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get account, %v", err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(stored, acct) {
|
|
||||||
t.Fatal("account is not matched")
|
|
||||||
}
|
|
||||||
for key, val := range storages[addr] {
|
|
||||||
stored, err := tr.GetStorage(addr, key.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get storage, %v", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(stored, val) {
|
|
||||||
t.Fatal("storage is not matched")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure there is some code in the 2nd group of the 1st account
|
|
||||||
keyOf2ndGroup := utils.CodeChunkKeyWithEvaluatedAddress(tr.cache.Get(common.Address{1}.Bytes()), uint256.NewInt(128))
|
|
||||||
chunk, err := tr.root.Get(keyOf2ndGroup, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get account, %v", err)
|
|
||||||
}
|
|
||||||
if len(chunk) == 0 {
|
|
||||||
t.Fatal("account was not created ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rollback first account and check that it is gone
|
|
||||||
addr1 := common.Address{1}
|
|
||||||
err = tr.RollBackAccount(addr1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error rolling back address 1: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure the account is gone
|
|
||||||
stored, err := tr.GetAccount(addr1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get account, %v", err)
|
|
||||||
}
|
|
||||||
if stored != nil {
|
|
||||||
t.Fatal("account was not deleted")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure that the last code chunk is also gone from the tree
|
|
||||||
chunk, err = tr.root.Get(keyOf2ndGroup, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to get account, %v", err)
|
|
||||||
}
|
|
||||||
if len(chunk) != 0 {
|
|
||||||
t.Fatal("account was not deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -163,12 +163,15 @@ type indexWriter struct {
|
||||||
db ethdb.KeyValueReader
|
db ethdb.KeyValueReader
|
||||||
}
|
}
|
||||||
|
|
||||||
// newIndexWriter constructs the index writer for the specified state.
|
// newIndexWriter constructs the index writer for the specified state. Additionally,
|
||||||
func newIndexWriter(db ethdb.KeyValueReader, state stateIdent) (*indexWriter, error) {
|
// it takes an integer as the limit and prunes all existing elements above that ID.
|
||||||
|
// It's essential as the recovery mechanism after unclean shutdown during the history
|
||||||
|
// indexing.
|
||||||
|
func newIndexWriter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*indexWriter, error) {
|
||||||
blob := readStateIndex(state, db)
|
blob := readStateIndex(state, db)
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
desc := newIndexBlockDesc(0)
|
desc := newIndexBlockDesc(0)
|
||||||
bw, _ := newBlockWriter(nil, desc)
|
bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */)
|
||||||
return &indexWriter{
|
return &indexWriter{
|
||||||
descList: []*indexBlockDesc{desc},
|
descList: []*indexBlockDesc{desc},
|
||||||
bw: bw,
|
bw: bw,
|
||||||
|
|
@ -180,15 +183,27 @@ func newIndexWriter(db ethdb.KeyValueReader, state stateIdent) (*indexWriter, er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Trim trailing blocks whose elements all exceed the limit.
|
||||||
|
for i := len(descList) - 1; i > 0 && descList[i].max > limit; i-- {
|
||||||
|
// The previous block has the elements that exceed the limit,
|
||||||
|
// therefore the current block can be entirely dropped.
|
||||||
|
if descList[i-1].max >= limit {
|
||||||
|
descList = descList[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Take the last block for appending new elements
|
||||||
lastDesc := descList[len(descList)-1]
|
lastDesc := descList[len(descList)-1]
|
||||||
indexBlock := readStateIndexBlock(state, db, lastDesc.id)
|
indexBlock := readStateIndexBlock(state, db, lastDesc.id)
|
||||||
bw, err := newBlockWriter(indexBlock, lastDesc)
|
|
||||||
|
// Construct the writer for the last block. All elements in this block
|
||||||
|
// that exceed the limit will be truncated.
|
||||||
|
bw, err := newBlockWriter(indexBlock, lastDesc, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &indexWriter{
|
return &indexWriter{
|
||||||
descList: descList,
|
descList: descList,
|
||||||
lastID: lastDesc.max,
|
lastID: bw.last(),
|
||||||
bw: bw,
|
bw: bw,
|
||||||
state: state,
|
state: state,
|
||||||
db: db,
|
db: db,
|
||||||
|
|
@ -221,7 +236,7 @@ func (w *indexWriter) rotate() error {
|
||||||
desc = newIndexBlockDesc(w.bw.desc.id + 1)
|
desc = newIndexBlockDesc(w.bw.desc.id + 1)
|
||||||
)
|
)
|
||||||
w.frozen = append(w.frozen, w.bw)
|
w.frozen = append(w.frozen, w.bw)
|
||||||
w.bw, err = newBlockWriter(nil, desc)
|
w.bw, err = newBlockWriter(nil, desc, 0 /* useless if the block is empty */)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -271,13 +286,13 @@ type indexDeleter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newIndexDeleter constructs the index deleter for the specified state.
|
// newIndexDeleter constructs the index deleter for the specified state.
|
||||||
func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent) (*indexDeleter, error) {
|
func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent, limit uint64) (*indexDeleter, error) {
|
||||||
blob := readStateIndex(state, db)
|
blob := readStateIndex(state, db)
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
// TODO(rjl493456442) we can probably return an error here,
|
// TODO(rjl493456442) we can probably return an error here,
|
||||||
// deleter with no data is meaningless.
|
// deleter with no data is meaningless.
|
||||||
desc := newIndexBlockDesc(0)
|
desc := newIndexBlockDesc(0)
|
||||||
bw, _ := newBlockWriter(nil, desc)
|
bw, _ := newBlockWriter(nil, desc, 0 /* useless if the block is empty */)
|
||||||
return &indexDeleter{
|
return &indexDeleter{
|
||||||
descList: []*indexBlockDesc{desc},
|
descList: []*indexBlockDesc{desc},
|
||||||
bw: bw,
|
bw: bw,
|
||||||
|
|
@ -289,22 +304,34 @@ func newIndexDeleter(db ethdb.KeyValueReader, state stateIdent) (*indexDeleter,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Trim trailing blocks whose elements all exceed the limit.
|
||||||
|
for i := len(descList) - 1; i > 0 && descList[i].max > limit; i-- {
|
||||||
|
// The previous block has the elements that exceed the limit,
|
||||||
|
// therefore the current block can be entirely dropped.
|
||||||
|
if descList[i-1].max >= limit {
|
||||||
|
descList = descList[:i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Take the block for deleting element from
|
||||||
lastDesc := descList[len(descList)-1]
|
lastDesc := descList[len(descList)-1]
|
||||||
indexBlock := readStateIndexBlock(state, db, lastDesc.id)
|
indexBlock := readStateIndexBlock(state, db, lastDesc.id)
|
||||||
bw, err := newBlockWriter(indexBlock, lastDesc)
|
|
||||||
|
// Construct the writer for the last block. All elements in this block
|
||||||
|
// that exceed the limit will be truncated.
|
||||||
|
bw, err := newBlockWriter(indexBlock, lastDesc, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &indexDeleter{
|
return &indexDeleter{
|
||||||
descList: descList,
|
descList: descList,
|
||||||
lastID: lastDesc.max,
|
lastID: bw.last(),
|
||||||
bw: bw,
|
bw: bw,
|
||||||
state: state,
|
state: state,
|
||||||
db: db,
|
db: db,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty returns an flag indicating whether the state index is empty.
|
// empty returns whether the state index is empty.
|
||||||
func (d *indexDeleter) empty() bool {
|
func (d *indexDeleter) empty() bool {
|
||||||
return d.bw.empty() && len(d.descList) == 1
|
return d.bw.empty() && len(d.descList) == 1
|
||||||
}
|
}
|
||||||
|
|
@ -337,7 +364,7 @@ func (d *indexDeleter) pop(id uint64) error {
|
||||||
// Open the previous block writer for deleting
|
// Open the previous block writer for deleting
|
||||||
lastDesc := d.descList[len(d.descList)-1]
|
lastDesc := d.descList[len(d.descList)-1]
|
||||||
indexBlock := readStateIndexBlock(d.state, d.db, lastDesc.id)
|
indexBlock := readStateIndexBlock(d.state, d.db, lastDesc.id)
|
||||||
bw, err := newBlockWriter(indexBlock, lastDesc)
|
bw, err := newBlockWriter(indexBlock, lastDesc, lastDesc.max)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,15 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
indexBlockDescSize = 14 // The size of index block descriptor
|
indexBlockDescSize = 14 // The size of index block descriptor
|
||||||
indexBlockEntriesCap = 4096 // The maximum number of entries can be grouped in a block
|
indexBlockEntriesCap = 4096 // The maximum number of entries can be grouped in a block
|
||||||
indexBlockRestartLen = 256 // The restart interval length of index block
|
indexBlockRestartLen = 256 // The restart interval length of index block
|
||||||
historyIndexBatch = 512 * 1024 // The number of state history indexes for constructing or deleting as batch
|
historyIndexBatch = 8 * 1024 * 1024 // The number of state history indexes for constructing or deleting as batch
|
||||||
)
|
)
|
||||||
|
|
||||||
// indexBlockDesc represents a descriptor for an index block, which contains a
|
// indexBlockDesc represents a descriptor for an index block, which contains a
|
||||||
|
|
@ -180,7 +182,11 @@ type blockWriter struct {
|
||||||
data []byte // Aggregated encoded data slice
|
data []byte // Aggregated encoded data slice
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBlockWriter(blob []byte, desc *indexBlockDesc) (*blockWriter, error) {
|
// newBlockWriter constructs a block writer. In addition to the existing data
|
||||||
|
// and block description, it takes an element ID and prunes all existing elements
|
||||||
|
// above that ID. It's essential as the recovery mechanism after unclean shutdown
|
||||||
|
// during the history indexing.
|
||||||
|
func newBlockWriter(blob []byte, desc *indexBlockDesc, limit uint64) (*blockWriter, error) {
|
||||||
if len(blob) == 0 {
|
if len(blob) == 0 {
|
||||||
return &blockWriter{
|
return &blockWriter{
|
||||||
desc: desc,
|
desc: desc,
|
||||||
|
|
@ -191,11 +197,22 @@ func newBlockWriter(blob []byte, desc *indexBlockDesc) (*blockWriter, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &blockWriter{
|
writer := &blockWriter{
|
||||||
desc: desc,
|
desc: desc,
|
||||||
restarts: restarts,
|
restarts: restarts,
|
||||||
data: data, // safe to own the slice
|
data: data, // safe to own the slice
|
||||||
}, nil
|
}
|
||||||
|
var trimmed int
|
||||||
|
for !writer.empty() && writer.last() > limit {
|
||||||
|
if err := writer.pop(writer.last()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
trimmed += 1
|
||||||
|
}
|
||||||
|
if trimmed > 0 {
|
||||||
|
log.Debug("Truncated extraneous elements", "count", trimmed, "limit", limit)
|
||||||
|
}
|
||||||
|
return writer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// append adds a new element to the block. The new element must be greater than
|
// append adds a new element to the block. The new element must be greater than
|
||||||
|
|
@ -271,6 +288,7 @@ func (b *blockWriter) sectionLast(section int) uint64 {
|
||||||
|
|
||||||
// sectionSearch looks up the specified value in the given section,
|
// sectionSearch looks up the specified value in the given section,
|
||||||
// the position and the preceding value will be returned if found.
|
// the position and the preceding value will be returned if found.
|
||||||
|
// It assumes that the preceding element exists in the section.
|
||||||
func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uint64, pos int) {
|
func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uint64, pos int) {
|
||||||
b.scanSection(section, func(v uint64, p int) bool {
|
b.scanSection(section, func(v uint64, p int) bool {
|
||||||
if n == v {
|
if n == v {
|
||||||
|
|
@ -295,7 +313,6 @@ func (b *blockWriter) pop(id uint64) error {
|
||||||
}
|
}
|
||||||
// If there is only one entry left, the entire block should be reset
|
// If there is only one entry left, the entire block should be reset
|
||||||
if b.desc.entries == 1 {
|
if b.desc.entries == 1 {
|
||||||
//b.desc.min = 0
|
|
||||||
b.desc.max = 0
|
b.desc.max = 0
|
||||||
b.desc.entries = 0
|
b.desc.entries = 0
|
||||||
b.restarts = nil
|
b.restarts = nil
|
||||||
|
|
@ -331,6 +348,15 @@ func (b *blockWriter) full() bool {
|
||||||
return b.desc.full()
|
return b.desc.full()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// last returns the last element in the block. It should only be called when
|
||||||
|
// writer is not empty, otherwise the returned data is meaningless.
|
||||||
|
func (b *blockWriter) last() uint64 {
|
||||||
|
if b.empty() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return b.desc.max
|
||||||
|
}
|
||||||
|
|
||||||
// finish finalizes the index block encoding by appending the encoded restart points
|
// finish finalizes the index block encoding by appending the encoded restart points
|
||||||
// and the restart counter to the end of the block.
|
// and the restart counter to the end of the block.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func TestBlockReaderBasic(t *testing.T) {
|
||||||
elements := []uint64{
|
elements := []uint64{
|
||||||
1, 5, 10, 11, 20,
|
1, 5, 10, 11, 20,
|
||||||
}
|
}
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
for i := 0; i < len(elements); i++ {
|
for i := 0; i < len(elements); i++ {
|
||||||
bw.append(elements[i])
|
bw.append(elements[i])
|
||||||
}
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ func TestBlockReaderLarge(t *testing.T) {
|
||||||
}
|
}
|
||||||
slices.Sort(elements)
|
slices.Sort(elements)
|
||||||
|
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
for i := 0; i < len(elements); i++ {
|
for i := 0; i < len(elements); i++ {
|
||||||
bw.append(elements[i])
|
bw.append(elements[i])
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ func TestBlockReaderLarge(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlockWriterBasic(t *testing.T) {
|
func TestBlockWriterBasic(t *testing.T) {
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
if !bw.empty() {
|
if !bw.empty() {
|
||||||
t.Fatal("expected empty block")
|
t.Fatal("expected empty block")
|
||||||
}
|
}
|
||||||
|
|
@ -103,11 +103,13 @@ func TestBlockWriterBasic(t *testing.T) {
|
||||||
if err := bw.append(1); err == nil {
|
if err := bw.append(1); err == nil {
|
||||||
t.Fatal("out-of-order insertion is not expected")
|
t.Fatal("out-of-order insertion is not expected")
|
||||||
}
|
}
|
||||||
|
var maxElem uint64
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
bw.append(uint64(i + 3))
|
bw.append(uint64(i + 3))
|
||||||
|
maxElem = uint64(i + 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0))
|
bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0), maxElem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to construct the block writer, %v", err)
|
t.Fatalf("Failed to construct the block writer, %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -119,8 +121,71 @@ func TestBlockWriterBasic(t *testing.T) {
|
||||||
bw.finish()
|
bw.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBlockWriterWithLimit(t *testing.T) {
|
||||||
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
|
|
||||||
|
var maxElem uint64
|
||||||
|
for i := 0; i < indexBlockRestartLen*2; i++ {
|
||||||
|
bw.append(uint64(i + 1))
|
||||||
|
maxElem = uint64(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
suites := []struct {
|
||||||
|
limit uint64
|
||||||
|
expMax uint64
|
||||||
|
}{
|
||||||
|
// nothing to truncate
|
||||||
|
{
|
||||||
|
maxElem, maxElem,
|
||||||
|
},
|
||||||
|
// truncate the last element
|
||||||
|
{
|
||||||
|
maxElem - 1, maxElem - 1,
|
||||||
|
},
|
||||||
|
// truncation around the restart boundary
|
||||||
|
{
|
||||||
|
uint64(indexBlockRestartLen + 1),
|
||||||
|
uint64(indexBlockRestartLen + 1),
|
||||||
|
},
|
||||||
|
// truncation around the restart boundary
|
||||||
|
{
|
||||||
|
uint64(indexBlockRestartLen),
|
||||||
|
uint64(indexBlockRestartLen),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uint64(1), uint64(1),
|
||||||
|
},
|
||||||
|
// truncate the entire block, it's in theory invalid
|
||||||
|
{
|
||||||
|
uint64(0), uint64(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, suite := range suites {
|
||||||
|
desc := *bw.desc
|
||||||
|
block, err := newBlockWriter(bw.finish(), &desc, suite.limit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to construct the block writer, %v", err)
|
||||||
|
}
|
||||||
|
if block.desc.max != suite.expMax {
|
||||||
|
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, block.desc.max, suite.expMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fill the elements
|
||||||
|
var maxElem uint64
|
||||||
|
for elem := suite.limit + 1; elem < indexBlockRestartLen*4; elem++ {
|
||||||
|
if err := block.append(elem); err != nil {
|
||||||
|
t.Fatalf("Failed to append value %d: %v", elem, err)
|
||||||
|
}
|
||||||
|
maxElem = elem
|
||||||
|
}
|
||||||
|
if block.desc.max != maxElem {
|
||||||
|
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, block.desc.max, maxElem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBlockWriterDelete(t *testing.T) {
|
func TestBlockWriterDelete(t *testing.T) {
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
bw.append(uint64(i + 1))
|
bw.append(uint64(i + 1))
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +212,7 @@ func TestBlcokWriterDeleteWithData(t *testing.T) {
|
||||||
elements := []uint64{
|
elements := []uint64{
|
||||||
1, 5, 10, 11, 20,
|
1, 5, 10, 11, 20,
|
||||||
}
|
}
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
for i := 0; i < len(elements); i++ {
|
for i := 0; i < len(elements); i++ {
|
||||||
bw.append(elements[i])
|
bw.append(elements[i])
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +223,7 @@ func TestBlcokWriterDeleteWithData(t *testing.T) {
|
||||||
max: 20,
|
max: 20,
|
||||||
entries: 5,
|
entries: 5,
|
||||||
}
|
}
|
||||||
bw, err := newBlockWriter(bw.finish(), desc)
|
bw, err := newBlockWriter(bw.finish(), desc, elements[len(elements)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to construct block writer %v", err)
|
t.Fatalf("Failed to construct block writer %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -201,15 +266,18 @@ func TestBlcokWriterDeleteWithData(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCorruptedIndexBlock(t *testing.T) {
|
func TestCorruptedIndexBlock(t *testing.T) {
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
|
|
||||||
|
var maxElem uint64
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
bw.append(uint64(i + 1))
|
bw.append(uint64(i + 1))
|
||||||
|
maxElem = uint64(i + 1)
|
||||||
}
|
}
|
||||||
buf := bw.finish()
|
buf := bw.finish()
|
||||||
|
|
||||||
// Mutate the buffer manually
|
// Mutate the buffer manually
|
||||||
buf[len(buf)-1]++
|
buf[len(buf)-1]++
|
||||||
_, err := newBlockWriter(buf, newIndexBlockDesc(0))
|
_, err := newBlockWriter(buf, newIndexBlockDesc(0), maxElem)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Corrupted index block data is not detected")
|
t.Fatal("Corrupted index block data is not detected")
|
||||||
}
|
}
|
||||||
|
|
@ -218,7 +286,7 @@ func TestCorruptedIndexBlock(t *testing.T) {
|
||||||
// BenchmarkParseIndexBlock benchmarks the performance of parseIndexBlock.
|
// BenchmarkParseIndexBlock benchmarks the performance of parseIndexBlock.
|
||||||
func BenchmarkParseIndexBlock(b *testing.B) {
|
func BenchmarkParseIndexBlock(b *testing.B) {
|
||||||
// Generate a realistic index block blob
|
// Generate a realistic index block blob
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
for i := 0; i < 4096; i++ {
|
for i := 0; i < 4096; i++ {
|
||||||
bw.append(uint64(i * 2))
|
bw.append(uint64(i * 2))
|
||||||
}
|
}
|
||||||
|
|
@ -238,13 +306,15 @@ func BenchmarkBlockWriterAppend(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
desc := newIndexBlockDesc(0)
|
var blockID uint32
|
||||||
writer, _ := newBlockWriter(nil, desc)
|
desc := newIndexBlockDesc(blockID)
|
||||||
|
writer, _ := newBlockWriter(nil, desc, 0)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if writer.full() {
|
if writer.full() {
|
||||||
desc = newIndexBlockDesc(0)
|
blockID += 1
|
||||||
writer, _ = newBlockWriter(nil, desc)
|
desc = newIndexBlockDesc(blockID)
|
||||||
|
writer, _ = newBlockWriter(nil, desc, 0)
|
||||||
}
|
}
|
||||||
if err := writer.append(writer.desc.max + 1); err != nil {
|
if err := writer.append(writer.desc.max + 1); err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func makeTestIndexBlock(count int) ([]byte, []uint64) {
|
||||||
marks = make(map[uint64]bool)
|
marks = make(map[uint64]bool)
|
||||||
elements []uint64
|
elements []uint64
|
||||||
)
|
)
|
||||||
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0))
|
bw, _ := newBlockWriter(nil, newIndexBlockDesc(0), 0)
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
n := uint64(rand.Uint32())
|
n := uint64(rand.Uint32())
|
||||||
if marks[n] {
|
if marks[n] {
|
||||||
|
|
@ -67,7 +67,7 @@ func makeTestIndexBlocks(db ethdb.KeyValueStore, stateIdent stateIdent, count in
|
||||||
}
|
}
|
||||||
sort.Slice(elements, func(i, j int) bool { return elements[i] < elements[j] })
|
sort.Slice(elements, func(i, j int) bool { return elements[i] < elements[j] })
|
||||||
|
|
||||||
iw, _ := newIndexWriter(db, stateIdent)
|
iw, _ := newIndexWriter(db, stateIdent, 0)
|
||||||
for i := 0; i < len(elements); i++ {
|
for i := 0; i < len(elements); i++ {
|
||||||
iw.append(elements[i])
|
iw.append(elements[i])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func TestIndexReaderBasic(t *testing.T) {
|
||||||
1, 5, 10, 11, 20,
|
1, 5, 10, 11, 20,
|
||||||
}
|
}
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
|
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||||
for i := 0; i < len(elements); i++ {
|
for i := 0; i < len(elements); i++ {
|
||||||
bw.append(elements[i])
|
bw.append(elements[i])
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ func TestIndexReaderLarge(t *testing.T) {
|
||||||
slices.Sort(elements)
|
slices.Sort(elements)
|
||||||
|
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
|
bw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||||
for i := 0; i < len(elements); i++ {
|
for i := 0; i < len(elements); i++ {
|
||||||
bw.append(elements[i])
|
bw.append(elements[i])
|
||||||
}
|
}
|
||||||
|
|
@ -122,19 +122,21 @@ func TestEmptyIndexReader(t *testing.T) {
|
||||||
|
|
||||||
func TestIndexWriterBasic(t *testing.T) {
|
func TestIndexWriterBasic(t *testing.T) {
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
|
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||||
iw.append(2)
|
iw.append(2)
|
||||||
if err := iw.append(1); err == nil {
|
if err := iw.append(1); err == nil {
|
||||||
t.Fatal("out-of-order insertion is not expected")
|
t.Fatal("out-of-order insertion is not expected")
|
||||||
}
|
}
|
||||||
|
var maxElem uint64
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
iw.append(uint64(i + 3))
|
iw.append(uint64(i + 3))
|
||||||
|
maxElem = uint64(i + 3)
|
||||||
}
|
}
|
||||||
batch := db.NewBatch()
|
batch := db.NewBatch()
|
||||||
iw.finish(batch)
|
iw.finish(batch)
|
||||||
batch.Write()
|
batch.Write()
|
||||||
|
|
||||||
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
|
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), maxElem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to construct the block writer, %v", err)
|
t.Fatalf("Failed to construct the block writer, %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -146,18 +148,87 @@ func TestIndexWriterBasic(t *testing.T) {
|
||||||
iw.finish(db.NewBatch())
|
iw.finish(db.NewBatch())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIndexWriterDelete(t *testing.T) {
|
func TestIndexWriterWithLimit(t *testing.T) {
|
||||||
db := rawdb.NewMemoryDatabase()
|
db := rawdb.NewMemoryDatabase()
|
||||||
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}))
|
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||||
|
|
||||||
|
var maxElem uint64
|
||||||
|
for i := 0; i < indexBlockEntriesCap*2; i++ {
|
||||||
|
iw.append(uint64(i + 1))
|
||||||
|
maxElem = uint64(i + 1)
|
||||||
|
}
|
||||||
|
batch := db.NewBatch()
|
||||||
|
iw.finish(batch)
|
||||||
|
batch.Write()
|
||||||
|
|
||||||
|
suites := []struct {
|
||||||
|
limit uint64
|
||||||
|
expMax uint64
|
||||||
|
}{
|
||||||
|
// nothing to truncate
|
||||||
|
{
|
||||||
|
maxElem, maxElem,
|
||||||
|
},
|
||||||
|
// truncate the last element
|
||||||
|
{
|
||||||
|
maxElem - 1, maxElem - 1,
|
||||||
|
},
|
||||||
|
// truncation around the block boundary
|
||||||
|
{
|
||||||
|
uint64(indexBlockEntriesCap + 1),
|
||||||
|
uint64(indexBlockEntriesCap + 1),
|
||||||
|
},
|
||||||
|
// truncation around the block boundary
|
||||||
|
{
|
||||||
|
uint64(indexBlockEntriesCap),
|
||||||
|
uint64(indexBlockEntriesCap),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uint64(1), uint64(1),
|
||||||
|
},
|
||||||
|
// truncate the entire index, it's in theory invalid
|
||||||
|
{
|
||||||
|
uint64(0), uint64(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, suite := range suites {
|
||||||
|
iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), suite.limit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to construct the index writer, %v", err)
|
||||||
|
}
|
||||||
|
if iw.lastID != suite.expMax {
|
||||||
|
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, iw.lastID, suite.expMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-fill the elements
|
||||||
|
var maxElem uint64
|
||||||
|
for elem := suite.limit + 1; elem < indexBlockEntriesCap*4; elem++ {
|
||||||
|
if err := iw.append(elem); err != nil {
|
||||||
|
t.Fatalf("Failed to append value %d: %v", elem, err)
|
||||||
|
}
|
||||||
|
maxElem = elem
|
||||||
|
}
|
||||||
|
if iw.lastID != maxElem {
|
||||||
|
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, iw.lastID, maxElem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIndexDeleterBasic(t *testing.T) {
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||||
|
|
||||||
|
var maxElem uint64
|
||||||
for i := 0; i < indexBlockEntriesCap*4; i++ {
|
for i := 0; i < indexBlockEntriesCap*4; i++ {
|
||||||
iw.append(uint64(i + 1))
|
iw.append(uint64(i + 1))
|
||||||
|
maxElem = uint64(i + 1)
|
||||||
}
|
}
|
||||||
batch := db.NewBatch()
|
batch := db.NewBatch()
|
||||||
iw.finish(batch)
|
iw.finish(batch)
|
||||||
batch.Write()
|
batch.Write()
|
||||||
|
|
||||||
// Delete unknown id, the request should be rejected
|
// Delete unknown id, the request should be rejected
|
||||||
id, _ := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}))
|
id, _ := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}), maxElem)
|
||||||
if err := id.pop(indexBlockEntriesCap * 5); err == nil {
|
if err := id.pop(indexBlockEntriesCap * 5); err == nil {
|
||||||
t.Fatal("Expect error to occur for unknown id")
|
t.Fatal("Expect error to occur for unknown id")
|
||||||
}
|
}
|
||||||
|
|
@ -168,10 +239,66 @@ func TestIndexWriterDelete(t *testing.T) {
|
||||||
if id.lastID != uint64(i-1) {
|
if id.lastID != uint64(i-1) {
|
||||||
t.Fatalf("Unexpected lastID, want: %d, got: %d", uint64(i-1), iw.lastID)
|
t.Fatalf("Unexpected lastID, want: %d, got: %d", uint64(i-1), iw.lastID)
|
||||||
}
|
}
|
||||||
if rand.Intn(10) == 0 {
|
}
|
||||||
batch := db.NewBatch()
|
}
|
||||||
id.finish(batch)
|
|
||||||
batch.Write()
|
func TestIndexDeleterWithLimit(t *testing.T) {
|
||||||
|
db := rawdb.NewMemoryDatabase()
|
||||||
|
iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa}), 0)
|
||||||
|
|
||||||
|
var maxElem uint64
|
||||||
|
for i := 0; i < indexBlockEntriesCap*2; i++ {
|
||||||
|
iw.append(uint64(i + 1))
|
||||||
|
maxElem = uint64(i + 1)
|
||||||
|
}
|
||||||
|
batch := db.NewBatch()
|
||||||
|
iw.finish(batch)
|
||||||
|
batch.Write()
|
||||||
|
|
||||||
|
suites := []struct {
|
||||||
|
limit uint64
|
||||||
|
expMax uint64
|
||||||
|
}{
|
||||||
|
// nothing to truncate
|
||||||
|
{
|
||||||
|
maxElem, maxElem,
|
||||||
|
},
|
||||||
|
// truncate the last element
|
||||||
|
{
|
||||||
|
maxElem - 1, maxElem - 1,
|
||||||
|
},
|
||||||
|
// truncation around the block boundary
|
||||||
|
{
|
||||||
|
uint64(indexBlockEntriesCap + 1),
|
||||||
|
uint64(indexBlockEntriesCap + 1),
|
||||||
|
},
|
||||||
|
// truncation around the block boundary
|
||||||
|
{
|
||||||
|
uint64(indexBlockEntriesCap),
|
||||||
|
uint64(indexBlockEntriesCap),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uint64(1), uint64(1),
|
||||||
|
},
|
||||||
|
// truncate the entire index, it's in theory invalid
|
||||||
|
{
|
||||||
|
uint64(0), uint64(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, suite := range suites {
|
||||||
|
id, err := newIndexDeleter(db, newAccountIdent(common.Hash{0xa}), suite.limit)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to construct the index writer, %v", err)
|
||||||
|
}
|
||||||
|
if id.lastID != suite.expMax {
|
||||||
|
t.Fatalf("Test %d, unexpected max value, got %d, want %d", i, id.lastID, suite.expMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep removing elements
|
||||||
|
for elem := id.lastID; elem > 0; elem-- {
|
||||||
|
if err := id.pop(elem); err != nil {
|
||||||
|
t.Fatalf("Failed to pop value %d: %v", elem, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,6 @@ const (
|
||||||
stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version
|
stateHistoryIndexVersion = stateHistoryIndexV0 // the current state index version
|
||||||
trienodeHistoryIndexV0 = uint8(0) // initial version of trienode index structure
|
trienodeHistoryIndexV0 = uint8(0) // initial version of trienode index structure
|
||||||
trienodeHistoryIndexVersion = trienodeHistoryIndexV0 // the current trienode index version
|
trienodeHistoryIndexVersion = trienodeHistoryIndexV0 // the current trienode index version
|
||||||
|
|
||||||
// estimations for calculating the batch size for atomic database commit
|
|
||||||
estimatedStateHistoryIndexSize = 3 // The average size of each state history index entry is approximately 2–3 bytes
|
|
||||||
estimatedTrienodeHistoryIndexSize = 3 // The average size of each trienode history index entry is approximately 2-3 bytes
|
|
||||||
estimatedIndexBatchSizeFactor = 32 // The factor counts for the write amplification for each entry
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// indexVersion returns the latest index version for the given history type.
|
// indexVersion returns the latest index version for the given history type.
|
||||||
|
|
@ -155,22 +150,6 @@ func (b *batchIndexer) process(h history, id uint64) error {
|
||||||
return b.finish(false)
|
return b.finish(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeBatch constructs a database batch based on the number of pending entries.
|
|
||||||
// The batch size is roughly estimated to minimize repeated resizing rounds,
|
|
||||||
// as accurately predicting the exact size is technically challenging.
|
|
||||||
func (b *batchIndexer) makeBatch() ethdb.Batch {
|
|
||||||
var size int
|
|
||||||
switch b.typ {
|
|
||||||
case typeStateHistory:
|
|
||||||
size = estimatedStateHistoryIndexSize
|
|
||||||
case typeTrienodeHistory:
|
|
||||||
size = estimatedTrienodeHistoryIndexSize
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unknown history type %d", b.typ))
|
|
||||||
}
|
|
||||||
return b.db.NewBatchWithSize(size * estimatedIndexBatchSizeFactor * b.pending)
|
|
||||||
}
|
|
||||||
|
|
||||||
// finish writes the accumulated state indexes into the disk if either the
|
// finish writes the accumulated state indexes into the disk if either the
|
||||||
// memory limitation is reached or it's requested forcibly.
|
// memory limitation is reached or it's requested forcibly.
|
||||||
func (b *batchIndexer) finish(force bool) error {
|
func (b *batchIndexer) finish(force bool) error {
|
||||||
|
|
@ -181,17 +160,38 @@ func (b *batchIndexer) finish(force bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
batch = b.makeBatch()
|
start = time.Now()
|
||||||
batchMu sync.RWMutex
|
eg errgroup.Group
|
||||||
start = time.Now()
|
|
||||||
eg errgroup.Group
|
batch = b.db.NewBatchWithSize(ethdb.IdealBatchSize)
|
||||||
|
batchSize int
|
||||||
|
batchMu sync.RWMutex
|
||||||
|
|
||||||
|
writeBatch = func(fn func(batch ethdb.Batch)) error {
|
||||||
|
batchMu.Lock()
|
||||||
|
defer batchMu.Unlock()
|
||||||
|
|
||||||
|
fn(batch)
|
||||||
|
if batch.ValueSize() >= ethdb.IdealBatchSize {
|
||||||
|
batchSize += batch.ValueSize()
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
batch.Reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
)
|
)
|
||||||
eg.SetLimit(runtime.NumCPU())
|
eg.SetLimit(runtime.NumCPU())
|
||||||
|
|
||||||
|
var indexed uint64
|
||||||
|
if metadata := loadIndexMetadata(b.db, b.typ); metadata != nil {
|
||||||
|
indexed = metadata.Last
|
||||||
|
}
|
||||||
for ident, list := range b.index {
|
for ident, list := range b.index {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if !b.delete {
|
if !b.delete {
|
||||||
iw, err := newIndexWriter(b.db, ident)
|
iw, err := newIndexWriter(b.db, ident, indexed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -200,11 +200,11 @@ func (b *batchIndexer) finish(force bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
batchMu.Lock()
|
return writeBatch(func(batch ethdb.Batch) {
|
||||||
iw.finish(batch)
|
iw.finish(batch)
|
||||||
batchMu.Unlock()
|
})
|
||||||
} else {
|
} else {
|
||||||
id, err := newIndexDeleter(b.db, ident)
|
id, err := newIndexDeleter(b.db, ident, indexed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -213,11 +213,10 @@ func (b *batchIndexer) finish(force bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
batchMu.Lock()
|
return writeBatch(func(batch ethdb.Batch) {
|
||||||
id.finish(batch)
|
id.finish(batch)
|
||||||
batchMu.Unlock()
|
})
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
|
|
@ -233,10 +232,12 @@ func (b *batchIndexer) finish(force bool) error {
|
||||||
storeIndexMetadata(batch, b.typ, b.lastID-1)
|
storeIndexMetadata(batch, b.typ, b.lastID-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
batchSize += batch.ValueSize()
|
||||||
|
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debug("Committed batch indexer", "type", b.typ, "entries", len(b.index), "records", b.pending, "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Debug("Committed batch indexer", "type", b.typ, "entries", len(b.index), "records", b.pending, "size", common.StorageSize(batchSize), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
b.pending = 0
|
b.pending = 0
|
||||||
b.index = make(map[stateIdent][]uint64)
|
b.index = make(map[stateIdent][]uint64)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue