mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
Merge branch 'master' into blobpool-gapped-queue
This commit is contained in:
commit
5557f2bbf3
96 changed files with 1623 additions and 3630 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ Audit reports are published in the `docs` folder: https://github.com/ethereum/go
|
||||||
|
|
||||||
To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publicly disclosed security vulnerabilities.
|
To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org. Please read the [disclosure page](https://github.com/ethereum/go-ethereum/security/advisories?state=published) for more information about publicly disclosed security vulnerabilities.
|
||||||
|
|
||||||
Use the built-in `geth version-check` feature to check whether the software is affected by any known vulnerability. This command will fetch the latest [`vulnerabilities.json`](https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json) file which contains known security vulnerabilities concerning `geth`, and cross-check the data against its own version number.
|
|
||||||
|
|
||||||
The following key may be used to communicate sensitive information to developers.
|
The following key may be used to communicate sensitive information to developers.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,6 @@ func init() {
|
||||||
javascriptCommand,
|
javascriptCommand,
|
||||||
// See misccmd.go:
|
// See misccmd.go:
|
||||||
versionCommand,
|
versionCommand,
|
||||||
versionCheckCommand,
|
|
||||||
licenseCommand,
|
licenseCommand,
|
||||||
// See config.go
|
// See config.go
|
||||||
dumpConfigCommand,
|
dumpConfigCommand,
|
||||||
|
|
@ -251,8 +250,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)
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
VersionCheckUrlFlag = &cli.StringFlag{
|
|
||||||
Name: "check.url",
|
|
||||||
Usage: "URL to use when checking vulnerabilities",
|
|
||||||
Value: "https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json",
|
|
||||||
}
|
|
||||||
VersionCheckVersionFlag = &cli.StringFlag{
|
|
||||||
Name: "check.version",
|
|
||||||
Usage: "Version to check",
|
|
||||||
Value: version.ClientName(clientIdentifier),
|
|
||||||
}
|
|
||||||
versionCommand = &cli.Command{
|
versionCommand = &cli.Command{
|
||||||
Action: printVersion,
|
Action: printVersion,
|
||||||
Name: "version",
|
Name: "version",
|
||||||
|
|
@ -44,20 +34,6 @@ var (
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
Description: `
|
Description: `
|
||||||
The output of this command is supposed to be machine-readable.
|
The output of this command is supposed to be machine-readable.
|
||||||
`,
|
|
||||||
}
|
|
||||||
versionCheckCommand = &cli.Command{
|
|
||||||
Action: versionCheck,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
VersionCheckUrlFlag,
|
|
||||||
VersionCheckVersionFlag,
|
|
||||||
},
|
|
||||||
Name: "version-check",
|
|
||||||
Usage: "Checks (online) for known Geth security vulnerabilities",
|
|
||||||
ArgsUsage: "<versionstring (optional)>",
|
|
||||||
Description: `
|
|
||||||
The version-check command fetches vulnerability-information from https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities.json,
|
|
||||||
and displays information about any security vulnerabilities that affect the currently executing version.
|
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
licenseCommand = &cli.Command{
|
licenseCommand = &cli.Command{
|
||||||
|
|
|
||||||
202
cmd/geth/testdata/vcheck/data.json
vendored
202
cmd/geth/testdata/vcheck/data.json
vendored
|
|
@ -1,202 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "CorruptedDAG",
|
|
||||||
"uid": "GETH-2020-01",
|
|
||||||
"summary": "Mining nodes will generate erroneous PoW on epochs > `385`.",
|
|
||||||
"description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/21793",
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p"
|
|
||||||
],
|
|
||||||
"introduced": "v1.6.0",
|
|
||||||
"fixed": "v1.9.24",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Medium",
|
|
||||||
"CVE": "CVE-2020-26240",
|
|
||||||
"check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Denial of service due to Go CVE-2020-28362",
|
|
||||||
"uid": "GETH-2020-02",
|
|
||||||
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`",
|
|
||||||
"description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.",
|
|
||||||
"links": [
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM",
|
|
||||||
"https://github.com/golang/go/issues/42552",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52"
|
|
||||||
],
|
|
||||||
"introduced": "v0.0.0",
|
|
||||||
"fixed": "v1.9.24",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Critical",
|
|
||||||
"CVE": "CVE-2020-28362",
|
|
||||||
"check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ShallowCopy",
|
|
||||||
"uid": "GETH-2020-03",
|
|
||||||
"summary": "A consensus flaw in Geth, related to `datacopy` precompile",
|
|
||||||
"description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.",
|
|
||||||
"links": [
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf"
|
|
||||||
],
|
|
||||||
"introduced": "v1.9.7",
|
|
||||||
"fixed": "v1.9.17",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Critical",
|
|
||||||
"CVE": "CVE-2020-26241",
|
|
||||||
"check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Geth DoS via MULMOD",
|
|
||||||
"uid": "GETH-2020-04",
|
|
||||||
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing",
|
|
||||||
"description": "Affected versions suffer from a vulnerability which can be exploited through the `MULMOD` operation, by specifying a modulo of `0`: `mulmod(a,b,0)`, causing a `panic` in the underlying library. \nThe crash was in the `uint256` library, where a buffer [underflowed](https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L442).\n\n\tif `d == 0`, `dLen` remains `0`\n\nand https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L451 will try to access index `[-1]`.\n\nThe `uint256` library was first merged in this [commit](https://github.com/ethereum/go-ethereum/commit/cf6674539c589f80031f3371a71c6a80addbe454), on 2020-06-08. \nExploiting this vulnerabilty would cause all vulnerable nodes to drop off the network. \n\nThe issue was brought to our attention through a [bug report](https://github.com/ethereum/go-ethereum/issues/21367), showing a `panic` occurring on sync from genesis on the Ropsten network.\n \nIt was estimated that the least obvious way to fix this would be to merge the fix into `uint256`, make a new release of that library and then update the geth-dependency.\n",
|
|
||||||
"links": [
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m",
|
|
||||||
"https://github.com/holiman/uint256/releases/tag/v1.1.1",
|
|
||||||
"https://github.com/holiman/uint256/pull/80",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/21368"
|
|
||||||
],
|
|
||||||
"introduced": "v1.9.16",
|
|
||||||
"fixed": "v1.9.18",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Critical",
|
|
||||||
"CVE": "CVE-2020-26242",
|
|
||||||
"check": "Geth\\/v1\\.9.(16|17).*$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LES Server DoS via GetProofsV2",
|
|
||||||
"uid": "GETH-2020-05",
|
|
||||||
"summary": "A DoS vulnerability can make a LES server crash.",
|
|
||||||
"description": "A DoS vulnerability can make a LES server crash via malicious GetProofsV2 request from a connected LES client.\n\nThe vulnerability was patched in #21896.\n\nThis vulnerability only concern users explicitly running geth as a light server",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-r33q-22hv-j29q",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/21896"
|
|
||||||
],
|
|
||||||
"introduced": "v1.8.0",
|
|
||||||
"fixed": "v1.9.25",
|
|
||||||
"published": "2020-12-10",
|
|
||||||
"severity": "Medium",
|
|
||||||
"CVE": "CVE-2020-26264",
|
|
||||||
"check": "(Geth\\/v1\\.8\\.*)|(Geth\\/v1\\.9\\.\\d-.*)|(Geth\\/v1\\.9\\.1\\d-.*)|(Geth\\/v1\\.9\\.(20|21|22|23|24)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SELFDESTRUCT-recreate consensus flaw",
|
|
||||||
"uid": "GETH-2020-06",
|
|
||||||
"introduced": "v1.9.4",
|
|
||||||
"fixed": "v1.9.20",
|
|
||||||
"summary": "A consensus-vulnerability in Geth could cause a chain split, where vulnerable versions refuse to accept the canonical chain.",
|
|
||||||
"description": "A flaw was repoted at 2020-08-11 by John Youngseok Yang (Software Platform Lab), where a particular sequence of transactions could cause a consensus failure.\n\n- Tx 1:\n - `sender` invokes `caller`.\n - `caller` invokes `0xaa`. `0xaa` has 3 wei, does a self-destruct-to-self\n - `caller` does a `1 wei` -call to `0xaa`, who thereby has 1 wei (the code in `0xaa` still executed, since the tx is still ongoing, but doesn't redo the selfdestruct, it takes a different path if callvalue is non-zero)\n\n-Tx 2:\n - `sender` does a 5-wei call to 0xaa. No exec (since no code). \n\nIn geth, the result would be that `0xaa` had `6 wei`, whereas OE reported (correctly) `5` wei. Furthermore, in geth, if the second tx was not executed, the `0xaa` would be destructed, resulting in `0 wei`. Thus obviously wrong. \n\nIt was determined that the root cause was this [commit](https://github.com/ethereum/go-ethereum/commit/223b950944f494a5b4e0957fd9f92c48b09037ad) from [this PR](https://github.com/ethereum/go-ethereum/pull/19953). The semantics of `createObject` was subtly changd, into returning a non-nil object (with `deleted=true`) where it previously did not if the account had been destructed. This return value caused the new object to inherit the old `balance`.\n",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-xw37-57qp-9mm4"
|
|
||||||
],
|
|
||||||
"published": "2020-12-10",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2020-26265",
|
|
||||||
"check": "(Geth\\/v1\\.9\\.(4|5|6|7|8|9)-.*)|(Geth\\/v1\\.9\\.1\\d-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Not ready for London upgrade",
|
|
||||||
"uid": "GETH-2021-01",
|
|
||||||
"summary": "The client is not ready for the 'London' technical upgrade, and will deviate from the canonical chain when the London upgrade occurs (at block '12965000' around August 4, 2021.",
|
|
||||||
"description": "At (or around) August 4, Ethereum will undergo a technical upgrade called 'London'. Clients not upgraded will fail to progress on the canonical chain.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md",
|
|
||||||
"https://notes.ethereum.org/@timbeiko/ropsten-postmortem"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.1",
|
|
||||||
"fixed": "v1.10.6",
|
|
||||||
"published": "2021-07-22",
|
|
||||||
"severity": "High",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(1|2|3|4|5)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "RETURNDATA corruption via datacopy",
|
|
||||||
"uid": "GETH-2021-02",
|
|
||||||
"summary": "A consensus-flaw in the Geth EVM could cause a node to deviate from the canonical chain.",
|
|
||||||
"description": "A memory-corruption bug within the EVM can cause a consensus error, where vulnerable nodes obtain a different `stateRoot` when processing a maliciously crafted transaction. This, in turn, would lead to the chain being split: mainnet splitting in two forks.\n\nAll Geth versions supporting the London hard fork are vulnerable (the bug is older than London), so all users should update.\n\nThis bug was exploited on Mainnet at block 13107518.\n\nCredits for the discovery go to @guidovranken (working for Sentnl during an audit of the Telos EVM) and reported via bounty@ethereum.org.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/blob/master/docs/postmortems/2021-08-22-split-postmortem.md",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-9856-9gg9-qcmq",
|
|
||||||
"https://github.com/ethereum/go-ethereum/releases/tag/v1.10.8"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.10.8",
|
|
||||||
"published": "2021-08-24",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2021-39137",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious `snap/1` request",
|
|
||||||
"uid": "GETH-2021-03",
|
|
||||||
"summary": "A vulnerable node is susceptible to crash when processing a maliciously crafted message from a peer, via the snap/1 protocol. The crash can be triggered by sending a malicious snap/1 GetTrieNodes package.",
|
|
||||||
"description": "The `snap/1` protocol handler contains two vulnerabilities related to the `GetTrieNodes` packet, which can be exploited to crash the node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v)",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/23657"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.10.9",
|
|
||||||
"published": "2021-10-24",
|
|
||||||
"severity": "Medium",
|
|
||||||
"CVE": "CVE-2021-41173",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious p2p message",
|
|
||||||
"uid": "GETH-2022-01",
|
|
||||||
"summary": "A vulnerable node can crash via p2p messages sent from an attacker node, if running with non-default log options.",
|
|
||||||
"description": "A vulnerable node, if configured to use high verbosity logging, can be made to crash when handling specially crafted p2p messages sent from an attacker node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5)",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/24507"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.10.17",
|
|
||||||
"published": "2022-05-11",
|
|
||||||
"severity": "Low",
|
|
||||||
"CVE": "CVE-2022-29177",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious p2p message",
|
|
||||||
"uid": "GETH-2023-01",
|
|
||||||
"summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
|
|
||||||
"description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.12.1",
|
|
||||||
"published": "2023-09-06",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2023-40591",
|
|
||||||
"check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious p2p message",
|
|
||||||
"uid": "GETH-2024-01",
|
|
||||||
"summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
|
|
||||||
"description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.13.15",
|
|
||||||
"published": "2024-05-06",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2024-32972",
|
|
||||||
"check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
untrusted comment: signature from minisign secret key
|
|
||||||
RUQkliYstQBOKHklFEYCUjepz81dyUuDmIAxjAvXa+icjGuKcjtVfV06G7qfOMSpplS5EcntU12n+AnGNyuOM8zIctaIWcfG2w0=
|
|
||||||
trusted comment: timestamp:1752094689 file:data.json hashed
|
|
||||||
u2e4wo4HBTU6viQTSY/NVBHoWoPFJnnTvLZS0FYl3JdvSOYi6+qpbEsDhAIFqq/n8VmlS/fPqqf7vKCNiAgjAA==
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
untrusted comment: signature from minisign secret key
|
|
||||||
RWQkliYstQBOKNoyq2O98hPmeVJQ6ShQLM58+4n0gkY0y0trFMDAsHuN/l4IyHfh8dDQ1ry0+IuZVrf/i8M/P3YFzFfAymDYCQ0=
|
|
||||||
trusted comment: timestamp:1752094703 file:data.json
|
|
||||||
cNyq3ZGlqo785HtWODb9ejWqF0HhSeXuLGXzC7z1IhnDrBObWBJngYd3qBG1dQcYlHQ+bgB/On5mSyMFn4UoCQ==
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
untrusted comment: Here's a comment
|
|
||||||
RWQkliYstQBOKNoyq2O98hPmeVJQ6ShQLM58+4n0gkY0y0trFMDAsHuN/l4IyHfh8dDQ1ry0+IuZVrf/i8M/P3YFzFfAymDYCQ0=
|
|
||||||
trusted comment: Here's a trusted comment
|
|
||||||
dL7lO8sqFFCOXJO/u8SgoDk2nlXGWPRDbOTJkChMbmtUp9PB7sG831basXkZ/0CQ/l/vG7AbPyMNEVZyJn5NCg==
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
untrusted comment: One more (untrusted™) comment
|
|
||||||
RWQkliYstQBOKNoyq2O98hPmeVJQ6ShQLM58+4n0gkY0y0trFMDAsHuN/l4IyHfh8dDQ1ry0+IuZVrf/i8M/P3YFzFfAymDYCQ0=
|
|
||||||
trusted comment: Here's a trusted comment
|
|
||||||
dL7lO8sqFFCOXJO/u8SgoDk2nlXGWPRDbOTJkChMbmtUp9PB7sG831basXkZ/0CQ/l/vG7AbPyMNEVZyJn5NCg==
|
|
||||||
2
cmd/geth/testdata/vcheck/minisign.pub
vendored
2
cmd/geth/testdata/vcheck/minisign.pub
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
untrusted comment: minisign public key 284E00B52C269624
|
|
||||||
RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp
|
|
||||||
2
cmd/geth/testdata/vcheck/minisign.sec
vendored
2
cmd/geth/testdata/vcheck/minisign.sec
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
untrusted comment: minisign encrypted secret key
|
|
||||||
RWRTY0Iyz8kmPMKrqk6DCtlO9a33akKiaOQG1aLolqDxs52qvPoAAAACAAAAAAAAAEAAAAAArEiggdvyn6+WzTprirLtgiYQoU+ihz/HyGgjhuF+Pz2ddMduyCO+xjCHeq+vgVVW039fbsI8hW6LRGJZLBKV5/jdxCXAVVQE7qTQ6xpEdO0z8Z731/pV1hlspQXG2PNd16NMtwd9dWw=
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
untrusted comment: verify with signifykey.pub
|
|
||||||
RWSKLNhZb0KdARbMcGN40hbHzKQYZDgDOFhEUT1YpzMnqre/mbKJ8td/HVlG03Am1YCszATiI0DbnljjTy4iNHYwqBfzrFUqUg0=
|
|
||||||
2
cmd/geth/testdata/vcheck/signifykey.pub
vendored
2
cmd/geth/testdata/vcheck/signifykey.pub
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
untrusted comment: signify public key
|
|
||||||
RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/
|
|
||||||
2
cmd/geth/testdata/vcheck/signifykey.sec
vendored
2
cmd/geth/testdata/vcheck/signifykey.sec
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
untrusted comment: signify secret key
|
|
||||||
RWRCSwAAACpLQDLawSQCtI7eAVIvaiHzjTsTyJsfV5aKLNhZb0KdAWeICXJGa93/bHAcsY6jUh9I8RdEcDWEoGxmaXZC+IdVBPxDpkix9fBRGEUdKWHi3dOfqME0YRzErWI5AVg3cRw=
|
|
||||||
202
cmd/geth/testdata/vcheck/vulnerabilities.json
vendored
202
cmd/geth/testdata/vcheck/vulnerabilities.json
vendored
|
|
@ -1,202 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "CorruptedDAG",
|
|
||||||
"uid": "GETH-2020-01",
|
|
||||||
"summary": "Mining nodes will generate erroneous PoW on epochs > `385`.",
|
|
||||||
"description": "A mining flaw could cause miners to erroneously calculate PoW, due to an index overflow, if DAG size is exceeding the maximum 32 bit unsigned value.\n\nThis occurred on the ETC chain on 2020-11-06. This is likely to trigger for ETH mainnet around block `11550000`/epoch `385`, slated to occur early January 2021.\n\nThis issue is relevant only for miners, non-mining nodes are unaffected, since non-mining nodes use a smaller verification cache instead of a full DAG.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/21793",
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://github.com/ethereum/go-ethereum/commit/567d41d9363706b4b13ce0903804e8acf214af49",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-v592-xf75-856p"
|
|
||||||
],
|
|
||||||
"introduced": "v1.6.0",
|
|
||||||
"fixed": "v1.9.24",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Medium",
|
|
||||||
"CVE": "CVE-2020-26240",
|
|
||||||
"check": "Geth\\/v1\\.(6|7|8)\\..*|Geth\\/v1\\.9\\.\\d-.*|Geth\\/v1\\.9\\.1.*|Geth\\/v1\\.9\\.2(0|1|2|3)-.*"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Denial of service due to Go CVE-2020-28362",
|
|
||||||
"uid": "GETH-2020-02",
|
|
||||||
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing, due to an underlying bug in Go (CVE-2020-28362) versions < `1.15.5`, or `<1.14.12`",
|
|
||||||
"description": "The DoS issue can be used to crash all Geth nodes during block processing, the effects of which would be that a major part of the Ethereum network went offline.\n\nOutside of Go-Ethereum, the issue is most likely relevant for all forks of Geth (such as TurboGeth or ETC’s core-geth) which is built with versions of Go which contains the vulnerability.",
|
|
||||||
"links": [
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://groups.google.com/g/golang-announce/c/NpBGTTmKzpM",
|
|
||||||
"https://github.com/golang/go/issues/42552",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-m6gx-rhvj-fh52"
|
|
||||||
],
|
|
||||||
"introduced": "v0.0.0",
|
|
||||||
"fixed": "v1.9.24",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Critical",
|
|
||||||
"CVE": "CVE-2020-28362",
|
|
||||||
"check": "Geth.*\\/go1\\.(11(.*)|12(.*)|13(.*)|14|14\\.(\\d|10|11|)|15|15\\.[0-4])$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ShallowCopy",
|
|
||||||
"uid": "GETH-2020-03",
|
|
||||||
"summary": "A consensus flaw in Geth, related to `datacopy` precompile",
|
|
||||||
"description": "Geth erroneously performed a 'shallow' copy when the precompiled `datacopy` (at `0x00...04`) was invoked. An attacker could deploy a contract that uses the shallow copy to corrupt the contents of the `RETURNDATA`, thus causing a consensus failure.",
|
|
||||||
"links": [
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-69v6-xc2j-r2jf"
|
|
||||||
],
|
|
||||||
"introduced": "v1.9.7",
|
|
||||||
"fixed": "v1.9.17",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Critical",
|
|
||||||
"CVE": "CVE-2020-26241",
|
|
||||||
"check": "Geth\\/v1\\.9\\.(7|8|9|10|11|12|13|14|15|16).*$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Geth DoS via MULMOD",
|
|
||||||
"uid": "GETH-2020-04",
|
|
||||||
"summary": "A denial-of-service issue can be used to crash Geth nodes during block processing",
|
|
||||||
"description": "Affected versions suffer from a vulnerability which can be exploited through the `MULMOD` operation, by specifying a modulo of `0`: `mulmod(a,b,0)`, causing a `panic` in the underlying library. \nThe crash was in the `uint256` library, where a buffer [underflowed](https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L442).\n\n\tif `d == 0`, `dLen` remains `0`\n\nand https://github.com/holiman/uint256/blob/4ce82e695c10ddad57215bdbeafb68b8c5df2c30/uint256.go#L451 will try to access index `[-1]`.\n\nThe `uint256` library was first merged in this [commit](https://github.com/ethereum/go-ethereum/commit/cf6674539c589f80031f3371a71c6a80addbe454), on 2020-06-08. \nExploiting this vulnerabilty would cause all vulnerable nodes to drop off the network. \n\nThe issue was brought to our attention through a [bug report](https://github.com/ethereum/go-ethereum/issues/21367), showing a `panic` occurring on sync from genesis on the Ropsten network.\n \nIt was estimated that the least obvious way to fix this would be to merge the fix into `uint256`, make a new release of that library and then update the geth-dependency.\n",
|
|
||||||
"links": [
|
|
||||||
"https://blog.ethereum.org/2020/11/12/geth-security-release",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-jm5c-rv3w-w83m",
|
|
||||||
"https://github.com/holiman/uint256/releases/tag/v1.1.1",
|
|
||||||
"https://github.com/holiman/uint256/pull/80",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/21368"
|
|
||||||
],
|
|
||||||
"introduced": "v1.9.16",
|
|
||||||
"fixed": "v1.9.18",
|
|
||||||
"published": "2020-11-12",
|
|
||||||
"severity": "Critical",
|
|
||||||
"CVE": "CVE-2020-26242",
|
|
||||||
"check": "Geth\\/v1\\.9.(16|17).*$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "LES Server DoS via GetProofsV2",
|
|
||||||
"uid": "GETH-2020-05",
|
|
||||||
"summary": "A DoS vulnerability can make a LES server crash.",
|
|
||||||
"description": "A DoS vulnerability can make a LES server crash via malicious GetProofsV2 request from a connected LES client.\n\nThe vulnerability was patched in #21896.\n\nThis vulnerability only concern users explicitly running geth as a light server",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-r33q-22hv-j29q",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/21896"
|
|
||||||
],
|
|
||||||
"introduced": "v1.8.0",
|
|
||||||
"fixed": "v1.9.25",
|
|
||||||
"published": "2020-12-10",
|
|
||||||
"severity": "Medium",
|
|
||||||
"CVE": "CVE-2020-26264",
|
|
||||||
"check": "(Geth\\/v1\\.8\\.*)|(Geth\\/v1\\.9\\.\\d-.*)|(Geth\\/v1\\.9\\.1\\d-.*)|(Geth\\/v1\\.9\\.(20|21|22|23|24)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SELFDESTRUCT-recreate consensus flaw",
|
|
||||||
"uid": "GETH-2020-06",
|
|
||||||
"introduced": "v1.9.4",
|
|
||||||
"fixed": "v1.9.20",
|
|
||||||
"summary": "A consensus-vulnerability in Geth could cause a chain split, where vulnerable versions refuse to accept the canonical chain.",
|
|
||||||
"description": "A flaw was repoted at 2020-08-11 by John Youngseok Yang (Software Platform Lab), where a particular sequence of transactions could cause a consensus failure.\n\n- Tx 1:\n - `sender` invokes `caller`.\n - `caller` invokes `0xaa`. `0xaa` has 3 wei, does a self-destruct-to-self\n - `caller` does a `1 wei` -call to `0xaa`, who thereby has 1 wei (the code in `0xaa` still executed, since the tx is still ongoing, but doesn't redo the selfdestruct, it takes a different path if callvalue is non-zero)\n\n-Tx 2:\n - `sender` does a 5-wei call to 0xaa. No exec (since no code). \n\nIn geth, the result would be that `0xaa` had `6 wei`, whereas OE reported (correctly) `5` wei. Furthermore, in geth, if the second tx was not executed, the `0xaa` would be destructed, resulting in `0 wei`. Thus obviously wrong. \n\nIt was determined that the root cause was this [commit](https://github.com/ethereum/go-ethereum/commit/223b950944f494a5b4e0957fd9f92c48b09037ad) from [this PR](https://github.com/ethereum/go-ethereum/pull/19953). The semantics of `createObject` was subtly changd, into returning a non-nil object (with `deleted=true`) where it previously did not if the account had been destructed. This return value caused the new object to inherit the old `balance`.\n",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-xw37-57qp-9mm4"
|
|
||||||
],
|
|
||||||
"published": "2020-12-10",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2020-26265",
|
|
||||||
"check": "(Geth\\/v1\\.9\\.(4|5|6|7|8|9)-.*)|(Geth\\/v1\\.9\\.1\\d-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Not ready for London upgrade",
|
|
||||||
"uid": "GETH-2021-01",
|
|
||||||
"summary": "The client is not ready for the 'London' technical upgrade, and will deviate from the canonical chain when the London upgrade occurs (at block '12965000' around August 4, 2021.",
|
|
||||||
"description": "At (or around) August 4, Ethereum will undergo a technical upgrade called 'London'. Clients not upgraded will fail to progress on the canonical chain.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/eth1.0-specs/blob/master/network-upgrades/mainnet-upgrades/london.md",
|
|
||||||
"https://notes.ethereum.org/@timbeiko/ropsten-postmortem"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.1",
|
|
||||||
"fixed": "v1.10.6",
|
|
||||||
"published": "2021-07-22",
|
|
||||||
"severity": "High",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(1|2|3|4|5)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "RETURNDATA corruption via datacopy",
|
|
||||||
"uid": "GETH-2021-02",
|
|
||||||
"summary": "A consensus-flaw in the Geth EVM could cause a node to deviate from the canonical chain.",
|
|
||||||
"description": "A memory-corruption bug within the EVM can cause a consensus error, where vulnerable nodes obtain a different `stateRoot` when processing a maliciously crafted transaction. This, in turn, would lead to the chain being split: mainnet splitting in two forks.\n\nAll Geth versions supporting the London hard fork are vulnerable (the bug is older than London), so all users should update.\n\nThis bug was exploited on Mainnet at block 13107518.\n\nCredits for the discovery go to @guidovranken (working for Sentnl during an audit of the Telos EVM) and reported via bounty@ethereum.org.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/blob/master/docs/postmortems/2021-08-22-split-postmortem.md",
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-9856-9gg9-qcmq",
|
|
||||||
"https://github.com/ethereum/go-ethereum/releases/tag/v1.10.8"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.10.8",
|
|
||||||
"published": "2021-08-24",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2021-39137",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious `snap/1` request",
|
|
||||||
"uid": "GETH-2021-03",
|
|
||||||
"summary": "A vulnerable node is susceptible to crash when processing a maliciously crafted message from a peer, via the snap/1 protocol. The crash can be triggered by sending a malicious snap/1 GetTrieNodes package.",
|
|
||||||
"description": "The `snap/1` protocol handler contains two vulnerabilities related to the `GetTrieNodes` packet, which can be exploited to crash the node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v)",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-59hh-656j-3p7v",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/23657"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.10.9",
|
|
||||||
"published": "2021-10-24",
|
|
||||||
"severity": "Medium",
|
|
||||||
"CVE": "CVE-2021-41173",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious p2p message",
|
|
||||||
"uid": "GETH-2022-01",
|
|
||||||
"summary": "A vulnerable node can crash via p2p messages sent from an attacker node, if running with non-default log options.",
|
|
||||||
"description": "A vulnerable node, if configured to use high verbosity logging, can be made to crash when handling specially crafted p2p messages sent from an attacker node. Full details are available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5)",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-wjxw-gh3m-7pm5",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities",
|
|
||||||
"https://github.com/ethereum/go-ethereum/pull/24507"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.10.17",
|
|
||||||
"published": "2022-05-11",
|
|
||||||
"severity": "Low",
|
|
||||||
"CVE": "CVE-2022-29177",
|
|
||||||
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious p2p message",
|
|
||||||
"uid": "GETH-2023-01",
|
|
||||||
"summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
|
|
||||||
"description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.12.1",
|
|
||||||
"published": "2023-09-06",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2023-40591",
|
|
||||||
"check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DoS via malicious p2p message",
|
|
||||||
"uid": "GETH-2024-01",
|
|
||||||
"summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
|
|
||||||
"description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)",
|
|
||||||
"links": [
|
|
||||||
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652",
|
|
||||||
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
|
|
||||||
],
|
|
||||||
"introduced": "v1.10.0",
|
|
||||||
"fixed": "v1.13.15",
|
|
||||||
"published": "2024-05-06",
|
|
||||||
"severity": "High",
|
|
||||||
"CVE": "CVE-2024-32972",
|
|
||||||
"check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
// Copyright 2020 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 (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/jedisct1/go-minisign"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var gethPubKeys []string = []string{
|
|
||||||
//@holiman, minisign public key FB1D084D39BAEC24
|
|
||||||
"RWQk7Lo5TQgd+wxBNZM+Zoy+7UhhMHaWKzqoes9tvSbFLJYZhNTbrIjx",
|
|
||||||
//minisign public key 138B1CA303E51687
|
|
||||||
"RWSHFuUDoxyLEzjszuWZI1xStS66QTyXFFZG18uDfO26CuCsbckX1e9J",
|
|
||||||
//minisign public key FD9813B2D2098484
|
|
||||||
"RWSEhAnSshOY/b+GmaiDkObbCWefsAoavjoLcPjBo1xn71yuOH5I+Lts",
|
|
||||||
}
|
|
||||||
|
|
||||||
type vulnJson struct {
|
|
||||||
Name string
|
|
||||||
Uid string
|
|
||||||
Summary string
|
|
||||||
Description string
|
|
||||||
Links []string
|
|
||||||
Introduced string
|
|
||||||
Fixed string
|
|
||||||
Published string
|
|
||||||
Severity string
|
|
||||||
Check string
|
|
||||||
CVE string
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionCheck(ctx *cli.Context) error {
|
|
||||||
url := ctx.String(VersionCheckUrlFlag.Name)
|
|
||||||
version := ctx.String(VersionCheckVersionFlag.Name)
|
|
||||||
log.Info("Checking vulnerabilities", "version", version, "url", url)
|
|
||||||
return checkCurrent(url, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCurrent(url, current string) error {
|
|
||||||
var (
|
|
||||||
data []byte
|
|
||||||
sig []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if data, err = fetch(url); err != nil {
|
|
||||||
return fmt.Errorf("could not retrieve data: %w", err)
|
|
||||||
}
|
|
||||||
if sig, err = fetch(fmt.Sprintf("%v.minisig", url)); err != nil {
|
|
||||||
return fmt.Errorf("could not retrieve signature: %w", err)
|
|
||||||
}
|
|
||||||
if err = verifySignature(gethPubKeys, data, sig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var vulns []vulnJson
|
|
||||||
if err = json.Unmarshal(data, &vulns); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
allOk := true
|
|
||||||
for _, vuln := range vulns {
|
|
||||||
r, err := regexp.Compile(vuln.Check)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.MatchString(current) {
|
|
||||||
allOk = false
|
|
||||||
fmt.Printf("## Vulnerable to %v (%v)\n\n", vuln.Uid, vuln.Name)
|
|
||||||
fmt.Printf("Severity: %v\n", vuln.Severity)
|
|
||||||
fmt.Printf("Summary : %v\n", vuln.Summary)
|
|
||||||
fmt.Printf("Fixed in: %v\n", vuln.Fixed)
|
|
||||||
if len(vuln.CVE) > 0 {
|
|
||||||
fmt.Printf("CVE: %v\n", vuln.CVE)
|
|
||||||
}
|
|
||||||
if len(vuln.Links) > 0 {
|
|
||||||
fmt.Printf("References:\n")
|
|
||||||
for _, ref := range vuln.Links {
|
|
||||||
fmt.Printf("\t- %v\n", ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if allOk {
|
|
||||||
fmt.Println("No vulnerabilities found")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch makes an HTTP request to the given url and returns the response body
|
|
||||||
func fetch(url string) ([]byte, error) {
|
|
||||||
if filep := strings.TrimPrefix(url, "file://"); filep != url {
|
|
||||||
return os.ReadFile(filep)
|
|
||||||
}
|
|
||||||
res, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
body, err := io.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifySignature checks that the sigData is a valid signature of the given
|
|
||||||
// data, for pubkey GethPubkey
|
|
||||||
func verifySignature(pubkeys []string, data, sigdata []byte) error {
|
|
||||||
sig, err := minisign.DecodeSignature(string(sigdata))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// find the used key
|
|
||||||
var key *minisign.PublicKey
|
|
||||||
for _, pubkey := range pubkeys {
|
|
||||||
pub, err := minisign.NewPublicKey(pubkey)
|
|
||||||
if err != nil {
|
|
||||||
// our pubkeys should be parseable
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pub.KeyId != sig.KeyId {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key = &pub
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if key == nil {
|
|
||||||
log.Info("Signing key not trusted", "keyid", keyID(sig.KeyId), "error", err)
|
|
||||||
return errors.New("signature could not be verified")
|
|
||||||
}
|
|
||||||
if ok, err := key.Verify(data, sig); !ok || err != nil {
|
|
||||||
log.Info("Verification failed error", "keyid", keyID(key.KeyId), "error", err)
|
|
||||||
return errors.New("signature could not be verified")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyID turns a binary minisign key ID into a hex string.
|
|
||||||
// Note: key IDs are printed in reverse byte order.
|
|
||||||
func keyID(id [8]byte) string {
|
|
||||||
var rev [8]byte
|
|
||||||
for i := range id {
|
|
||||||
rev[len(rev)-1-i] = id[i]
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%X", rev)
|
|
||||||
}
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
// Copyright 2020 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 (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/jedisct1/go-minisign"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVerification(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
// Signatures generated with `minisign`. Legacy format, not pre-hashed file.
|
|
||||||
t.Run("minisig-legacy", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
// For this test, the pubkey is in testdata/vcheck/minisign.pub
|
|
||||||
// (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' )
|
|
||||||
// 1. `minisign -S -l -s ./minisign.sec -m data.json -x ./minisig-sigs/vulnerabilities.json.minisig.1 -c "signature from minisign secret key"`
|
|
||||||
// 2. `minisign -S -l -s ./minisign.sec -m vulnerabilities.json -x ./minisig-sigs/vulnerabilities.json.minisig.2 -c "Here's a comment" -t "Here's a trusted comment"`
|
|
||||||
// 3. minisign -S -l -s ./minisign.sec -m vulnerabilities.json -x ./minisig-sigs/vulnerabilities.json.minisig.3 -c "One more (untrusted™) comment" -t "Here's a trusted comment"
|
|
||||||
pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp"
|
|
||||||
testVerification(t, pub, "./testdata/vcheck/minisig-sigs/")
|
|
||||||
})
|
|
||||||
t.Run("minisig-new", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
// For this test, the pubkey is in testdata/vcheck/minisign.pub
|
|
||||||
// (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' )
|
|
||||||
// `minisign -S -s ./minisign.sec -m data.json -x ./minisig-sigs-new/data.json.minisig`
|
|
||||||
pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp"
|
|
||||||
testVerification(t, pub, "./testdata/vcheck/minisig-sigs-new/")
|
|
||||||
})
|
|
||||||
// Signatures generated with `signify-openbsd`
|
|
||||||
t.Run("signify-openbsd", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2")
|
|
||||||
// For this test, the pubkey is in testdata/vcheck/signifykey.pub
|
|
||||||
// (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' )
|
|
||||||
// `signify -S -s signifykey.sec -m data.json -x ./signify-sigs/data.json.sig`
|
|
||||||
pub := "RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/"
|
|
||||||
testVerification(t, pub, "./testdata/vcheck/signify-sigs/")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testVerification(t *testing.T, pubkey, sigdir string) {
|
|
||||||
// Data to verify
|
|
||||||
data, err := os.ReadFile("./testdata/vcheck/data.json")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// Signatures, with and without comments, both trusted and untrusted
|
|
||||||
files, err := os.ReadDir(sigdir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(files) == 0 {
|
|
||||||
t.Fatal("Missing tests")
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
sig, err := os.ReadFile(filepath.Join(sigdir, f.Name()))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = verifySignature([]string{pubkey}, data, sig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionUint(v string) int {
|
|
||||||
mustInt := func(s string) int {
|
|
||||||
a, err := strconv.Atoi(s)
|
|
||||||
if err != nil {
|
|
||||||
panic(v)
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
components := strings.Split(strings.TrimPrefix(v, "v"), ".")
|
|
||||||
a := mustInt(components[0])
|
|
||||||
b := mustInt(components[1])
|
|
||||||
c := mustInt(components[2])
|
|
||||||
return a*100*100 + b*100 + c
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMatching can be used to check that the regexps are correct
|
|
||||||
func TestMatching(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
data, _ := os.ReadFile("./testdata/vcheck/vulnerabilities.json")
|
|
||||||
var vulns []vulnJson
|
|
||||||
if err := json.Unmarshal(data, &vulns); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
check := func(version string) {
|
|
||||||
vFull := fmt.Sprintf("Geth/%v-unstable-15339cf1-20201204/linux-amd64/go1.15.4", version)
|
|
||||||
for _, vuln := range vulns {
|
|
||||||
r, err := regexp.Compile(vuln.Check)
|
|
||||||
vulnIntro := versionUint(vuln.Introduced)
|
|
||||||
vulnFixed := versionUint(vuln.Fixed)
|
|
||||||
current := versionUint(version)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if vuln.Name == "Denial of service due to Go CVE-2020-28362" {
|
|
||||||
// this one is not tied to geth-versions
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if vulnIntro <= current && vulnFixed > current {
|
|
||||||
// Should be vulnerable
|
|
||||||
if !r.MatchString(vFull) {
|
|
||||||
t.Errorf("Should be vulnerable, version %v, intro: %v, fixed: %v %v %v",
|
|
||||||
version, vuln.Introduced, vuln.Fixed, vuln.Name, vuln.Check)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if r.MatchString(vFull) {
|
|
||||||
t.Errorf("Should not be flagged vulnerable, version %v, intro: %v, fixed: %v %v %d %d %d",
|
|
||||||
version, vuln.Introduced, vuln.Fixed, vuln.Name, vulnIntro, current, vulnFixed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for major := 1; major < 2; major++ {
|
|
||||||
for minor := 0; minor < 30; minor++ {
|
|
||||||
for patch := 0; patch < 30; patch++ {
|
|
||||||
vShort := fmt.Sprintf("v%d.%d.%d", major, minor, patch)
|
|
||||||
check(vShort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGethPubKeysParseable(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
for _, pubkey := range gethPubKeys {
|
|
||||||
_, err := minisign.NewPublicKey(pubkey)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Should be parseable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyID(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
type args struct {
|
|
||||||
id [8]byte
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"@holiman key", args{id: extractKeyId(gethPubKeys[0])}, "FB1D084D39BAEC24"},
|
|
||||||
{"second key", args{id: extractKeyId(gethPubKeys[1])}, "138B1CA303E51687"},
|
|
||||||
{"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if got := keyID(tt.args.id); got != tt.want {
|
|
||||||
t.Errorf("keyID() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractKeyId(pubkey string) [8]byte {
|
|
||||||
p, _ := minisign.NewPublicKey(pubkey)
|
|
||||||
return p.KeyId
|
|
||||||
}
|
|
||||||
|
|
@ -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=
|
||||||
|
|
|
||||||
|
|
@ -1430,7 +1430,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
||||||
cfg.KeyStoreDir = ctx.String(KeyStoreDirFlag.Name)
|
cfg.KeyStoreDir = ctx.String(KeyStoreDirFlag.Name)
|
||||||
}
|
}
|
||||||
if ctx.IsSet(DeveloperFlag.Name) {
|
if ctx.IsSet(DeveloperFlag.Name) {
|
||||||
cfg.UseLightweightKDF = true
|
cfg.UseLightweightKDF = ctx.Bool(DeveloperFlag.Name)
|
||||||
}
|
}
|
||||||
if ctx.IsSet(LightKDFFlag.Name) {
|
if ctx.IsSet(LightKDFFlag.Name) {
|
||||||
cfg.UseLightweightKDF = ctx.Bool(LightKDFFlag.Name)
|
cfg.UseLightweightKDF = ctx.Bool(LightKDFFlag.Name)
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,5 @@ the following commands (in this directory) against a synced mainnet node:
|
||||||
> go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545
|
> go run . filtergen --queries queries/filter_queries_mainnet.json http://host:8545
|
||||||
> go run . historygen --history-tests queries/history_mainnet.json http://host:8545
|
> go run . historygen --history-tests queries/history_mainnet.json http://host:8545
|
||||||
> go run . tracegen --trace-tests queries/trace_mainnet.json --trace-start 4000000 --trace-end 4000100 http://host:8545
|
> go run . tracegen --trace-tests queries/trace_mainnet.json --trace-start 4000000 --trace-end 4000100 http://host:8545
|
||||||
|
> go run . proofgen --proof-tests queries/proof_mainnet.json --proof-states 3000 http://host:8545
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ func init() {
|
||||||
historyGenerateCommand,
|
historyGenerateCommand,
|
||||||
filterGenerateCommand,
|
filterGenerateCommand,
|
||||||
traceGenerateCommand,
|
traceGenerateCommand,
|
||||||
|
proofGenerateCommand,
|
||||||
filterPerfCommand,
|
filterPerfCommand,
|
||||||
filterFuzzCommand,
|
filterFuzzCommand,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
105
cmd/workload/prooftest.go
Normal file
105
cmd/workload/prooftest.go
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
// Copyright 2025 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 (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/utesting"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// proofTest is the content of a state-proof test.
|
||||||
|
type proofTest struct {
|
||||||
|
BlockNumbers []uint64 `json:"blockNumbers"`
|
||||||
|
Addresses [][]common.Address `json:"addresses"`
|
||||||
|
StorageKeys [][][]string `json:"storageKeys"`
|
||||||
|
Results [][]common.Hash `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type proofTestSuite struct {
|
||||||
|
cfg testConfig
|
||||||
|
tests proofTest
|
||||||
|
invalidDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProofTestSuite(cfg testConfig, ctx *cli.Context) *proofTestSuite {
|
||||||
|
s := &proofTestSuite{
|
||||||
|
cfg: cfg,
|
||||||
|
invalidDir: ctx.String(proofTestInvalidOutputFlag.Name),
|
||||||
|
}
|
||||||
|
if err := s.loadTests(); err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *proofTestSuite) loadTests() error {
|
||||||
|
file, err := s.cfg.fsys.Open(s.cfg.proofTestFile)
|
||||||
|
if err != nil {
|
||||||
|
// If not found in embedded FS, try to load it from disk
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file, err = os.OpenFile(s.cfg.proofTestFile, os.O_RDONLY, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't open proofTestFile: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
if err := json.NewDecoder(file).Decode(&s.tests); err != nil {
|
||||||
|
return fmt.Errorf("invalid JSON in %s: %v", s.cfg.proofTestFile, err)
|
||||||
|
}
|
||||||
|
if len(s.tests.BlockNumbers) == 0 {
|
||||||
|
return fmt.Errorf("proofTestFile %s has no test data", s.cfg.proofTestFile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *proofTestSuite) allTests() []workloadTest {
|
||||||
|
return []workloadTest{
|
||||||
|
newArchiveWorkloadTest("Proof/GetProof", s.getProof),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *proofTestSuite) getProof(t *utesting.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
for i, blockNumber := range s.tests.BlockNumbers {
|
||||||
|
for j := 0; j < len(s.tests.Addresses[i]); j++ {
|
||||||
|
res, err := s.cfg.client.Geth.GetProof(ctx, s.tests.Addresses[i][j], s.tests.StorageKeys[i][j], big.NewInt(int64(blockNumber)))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("State proving fails, blockNumber: %d, address: %x, keys: %v, err: %v\n", blockNumber, s.tests.Addresses[i][j], strings.Join(s.tests.StorageKeys[i][j], " "), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blob, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("State proving fails: error %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if crypto.Keccak256Hash(blob) != s.tests.Results[i][j] {
|
||||||
|
t.Errorf("State proof mismatch, %d, number: %d, address: %x, keys: %v: invalid result", i, blockNumber, s.tests.Addresses[i][j], strings.Join(s.tests.StorageKeys[i][j], " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
355
cmd/workload/prooftestgen.go
Normal file
355
cmd/workload/prooftestgen.go
Normal file
|
|
@ -0,0 +1,355 @@
|
||||||
|
// Copyright 2025 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 (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers/native"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/flags"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
proofGenerateCommand = &cli.Command{
|
||||||
|
Name: "proofgen",
|
||||||
|
Usage: "Generates tests for state proof verification",
|
||||||
|
ArgsUsage: "<RPC endpoint URL>",
|
||||||
|
Action: generateProofTests,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
proofTestFileFlag,
|
||||||
|
proofTestResultOutputFlag,
|
||||||
|
proofTestStatesFlag,
|
||||||
|
proofTestStartBlockFlag,
|
||||||
|
proofTestEndBlockFlag,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
proofTestFileFlag = &cli.StringFlag{
|
||||||
|
Name: "proof-tests",
|
||||||
|
Usage: "JSON file containing proof test queries",
|
||||||
|
Value: "proof_tests.json",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
proofTestResultOutputFlag = &cli.StringFlag{
|
||||||
|
Name: "proof-output",
|
||||||
|
Usage: "Folder containing detailed trace output files",
|
||||||
|
Value: "",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
proofTestStatesFlag = &cli.Int64Flag{
|
||||||
|
Name: "proof-states",
|
||||||
|
Usage: "Number of states to generate proof against",
|
||||||
|
Value: 10000,
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
proofTestInvalidOutputFlag = &cli.StringFlag{
|
||||||
|
Name: "proof-invalid",
|
||||||
|
Usage: "Folder containing the mismatched state proof output files",
|
||||||
|
Value: "",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
proofTestStartBlockFlag = &cli.Uint64Flag{
|
||||||
|
Name: "proof-start",
|
||||||
|
Usage: "The number of starting block for proof verification (included)",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
proofTestEndBlockFlag = &cli.Uint64Flag{
|
||||||
|
Name: "proof-end",
|
||||||
|
Usage: "The number of ending block for proof verification (excluded)",
|
||||||
|
Category: flags.TestingCategory,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type proofGenerator func(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error)
|
||||||
|
|
||||||
|
func genAccountProof(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error) {
|
||||||
|
var (
|
||||||
|
blockNumbers []uint64
|
||||||
|
accountAddresses [][]common.Address
|
||||||
|
storageKeys [][][]string
|
||||||
|
nAccounts int
|
||||||
|
ctx = context.Background()
|
||||||
|
start = time.Now()
|
||||||
|
)
|
||||||
|
chainID, err := cli.Eth.ChainID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
signer := types.LatestSignerForChainID(chainID)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if nAccounts >= number {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
blockNumber := uint64(rand.Intn(int(endBlock-startBlock))) + startBlock
|
||||||
|
|
||||||
|
block, err := cli.Eth.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber)))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
addresses []common.Address
|
||||||
|
keys [][]string
|
||||||
|
gather = func(address common.Address) {
|
||||||
|
addresses = append(addresses, address)
|
||||||
|
keys = append(keys, nil)
|
||||||
|
nAccounts++
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
if nAccounts >= number {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sender, err := signer.Sender(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to resolve the sender address", "hash", tx.Hash(), "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gather(sender)
|
||||||
|
|
||||||
|
if tx.To() != nil {
|
||||||
|
gather(*tx.To())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blockNumbers = append(blockNumbers, blockNumber)
|
||||||
|
accountAddresses = append(accountAddresses, addresses)
|
||||||
|
storageKeys = append(storageKeys, keys)
|
||||||
|
}
|
||||||
|
log.Info("Generated tests for account proof", "blocks", len(blockNumbers), "accounts", nAccounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
return blockNumbers, accountAddresses, storageKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genNonExistentAccountProof(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error) {
|
||||||
|
var (
|
||||||
|
blockNumbers []uint64
|
||||||
|
accountAddresses [][]common.Address
|
||||||
|
storageKeys [][][]string
|
||||||
|
total int
|
||||||
|
)
|
||||||
|
for i := 0; i < number/5; i++ {
|
||||||
|
var (
|
||||||
|
addresses []common.Address
|
||||||
|
keys [][]string
|
||||||
|
blockNumber = uint64(rand.Intn(int(endBlock-startBlock))) + startBlock
|
||||||
|
)
|
||||||
|
for j := 0; j < 5; j++ {
|
||||||
|
addresses = append(addresses, testrand.Address())
|
||||||
|
keys = append(keys, nil)
|
||||||
|
}
|
||||||
|
total += len(addresses)
|
||||||
|
blockNumbers = append(blockNumbers, blockNumber)
|
||||||
|
accountAddresses = append(accountAddresses, addresses)
|
||||||
|
storageKeys = append(storageKeys, keys)
|
||||||
|
}
|
||||||
|
log.Info("Generated tests for non-existing account proof", "blocks", len(blockNumbers), "accounts", total)
|
||||||
|
return blockNumbers, accountAddresses, storageKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genStorageProof(cli *client, startBlock uint64, endBlock uint64, number int) ([]uint64, [][]common.Address, [][][]string, error) {
|
||||||
|
var (
|
||||||
|
blockNumbers []uint64
|
||||||
|
accountAddresses [][]common.Address
|
||||||
|
storageKeys [][][]string
|
||||||
|
|
||||||
|
nAccounts int
|
||||||
|
nStorages int
|
||||||
|
start = time.Now()
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
if nAccounts+nStorages >= number {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
blockNumber := uint64(rand.Intn(int(endBlock-startBlock))) + startBlock
|
||||||
|
|
||||||
|
block, err := cli.Eth.BlockByNumber(context.Background(), big.NewInt(int64(blockNumber)))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
addresses []common.Address
|
||||||
|
slots [][]string
|
||||||
|
tracer = "prestateTracer"
|
||||||
|
configBlob, _ = json.Marshal(native.PrestateTracerConfig{
|
||||||
|
DiffMode: false,
|
||||||
|
DisableCode: true,
|
||||||
|
DisableStorage: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
if nAccounts+nStorages >= number {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tx.To() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret, err := cli.Geth.TraceTransaction(context.Background(), tx.Hash(), &tracers.TraceConfig{
|
||||||
|
Tracer: &tracer,
|
||||||
|
TracerConfig: configBlob,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to trace the transaction", "blockNumber", blockNumber, "hash", tx.Hash(), "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blob, err := json.Marshal(ret)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to marshal data", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var accounts map[common.Address]*types.Account
|
||||||
|
if err := json.Unmarshal(blob, &accounts); err != nil {
|
||||||
|
log.Error("Failed to decode trace result", "blockNumber", blockNumber, "hash", tx.Hash(), "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for addr, account := range accounts {
|
||||||
|
if len(account.Storage) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addresses = append(addresses, addr)
|
||||||
|
nAccounts += 1
|
||||||
|
|
||||||
|
var keys []string
|
||||||
|
for k := range account.Storage {
|
||||||
|
keys = append(keys, k.Hex())
|
||||||
|
}
|
||||||
|
nStorages += len(keys)
|
||||||
|
|
||||||
|
var emptyKeys []string
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
emptyKeys = append(emptyKeys, testrand.Hash().Hex())
|
||||||
|
}
|
||||||
|
nStorages += len(emptyKeys)
|
||||||
|
|
||||||
|
slots = append(slots, append(keys, emptyKeys...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blockNumbers = append(blockNumbers, blockNumber)
|
||||||
|
accountAddresses = append(accountAddresses, addresses)
|
||||||
|
storageKeys = append(storageKeys, slots)
|
||||||
|
}
|
||||||
|
log.Info("Generated tests for storage proof", "blocks", len(blockNumbers), "accounts", nAccounts, "storages", nStorages, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
return blockNumbers, accountAddresses, storageKeys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genProofRequests(cli *client, startBlock, endBlock uint64, states int) (*proofTest, error) {
|
||||||
|
var (
|
||||||
|
blockNumbers []uint64
|
||||||
|
accountAddresses [][]common.Address
|
||||||
|
storageKeys [][][]string
|
||||||
|
)
|
||||||
|
ratio := []float64{0.2, 0.1, 0.7}
|
||||||
|
for i, fn := range []proofGenerator{genAccountProof, genNonExistentAccountProof, genStorageProof} {
|
||||||
|
numbers, addresses, keys, err := fn(cli, startBlock, endBlock, int(float64(states)*ratio[i]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blockNumbers = append(blockNumbers, numbers...)
|
||||||
|
accountAddresses = append(accountAddresses, addresses...)
|
||||||
|
storageKeys = append(storageKeys, keys...)
|
||||||
|
}
|
||||||
|
return &proofTest{
|
||||||
|
BlockNumbers: blockNumbers,
|
||||||
|
Addresses: accountAddresses,
|
||||||
|
StorageKeys: storageKeys,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateProofTests(clictx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
client = makeClient(clictx)
|
||||||
|
ctx = context.Background()
|
||||||
|
states = clictx.Int(proofTestStatesFlag.Name)
|
||||||
|
outputFile = clictx.String(proofTestFileFlag.Name)
|
||||||
|
outputDir = clictx.String(proofTestResultOutputFlag.Name)
|
||||||
|
startBlock = clictx.Uint64(proofTestStartBlockFlag.Name)
|
||||||
|
endBlock = clictx.Uint64(proofTestEndBlockFlag.Name)
|
||||||
|
)
|
||||||
|
head, err := client.Eth.BlockNumber(ctx)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
if startBlock > head || endBlock > head {
|
||||||
|
return fmt.Errorf("chain is out of proof range, head %d, start: %d, limit: %d", head, startBlock, endBlock)
|
||||||
|
}
|
||||||
|
if endBlock == 0 {
|
||||||
|
endBlock = head
|
||||||
|
}
|
||||||
|
log.Info("Generating proof states", "startBlock", startBlock, "endBlock", endBlock, "states", states)
|
||||||
|
|
||||||
|
test, err := genProofRequests(client, startBlock, endBlock, states)
|
||||||
|
if err != nil {
|
||||||
|
exit(err)
|
||||||
|
}
|
||||||
|
for i, blockNumber := range test.BlockNumbers {
|
||||||
|
var hashes []common.Hash
|
||||||
|
for j := 0; j < len(test.Addresses[i]); j++ {
|
||||||
|
res, err := client.Geth.GetProof(ctx, test.Addresses[i][j], test.StorageKeys[i][j], big.NewInt(int64(blockNumber)))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to prove the state", "number", blockNumber, "address", test.Addresses[i][j], "slots", len(test.StorageKeys[i][j]), "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blob, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hashes = append(hashes, crypto.Keccak256Hash(blob))
|
||||||
|
|
||||||
|
writeStateProof(outputDir, blockNumber, test.Addresses[i][j], res)
|
||||||
|
}
|
||||||
|
test.Results = append(test.Results, hashes)
|
||||||
|
}
|
||||||
|
writeJSON(outputFile, test)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStateProof(dir string, blockNumber uint64, address common.Address, result any) {
|
||||||
|
if dir == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Ensure the directory exists
|
||||||
|
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||||
|
exit(fmt.Errorf("failed to create directories: %w", err))
|
||||||
|
}
|
||||||
|
fname := fmt.Sprintf("%d-%x", blockNumber, address)
|
||||||
|
name := filepath.Join(dir, fname)
|
||||||
|
file, err := os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
exit(fmt.Errorf("error creating %s: %v", name, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
data, _ := json.MarshalIndent(result, "", " ")
|
||||||
|
_, err = file.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
exit(fmt.Errorf("error writing %s: %v", name, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,9 @@ var (
|
||||||
filterQueryFileFlag,
|
filterQueryFileFlag,
|
||||||
historyTestFileFlag,
|
historyTestFileFlag,
|
||||||
traceTestFileFlag,
|
traceTestFileFlag,
|
||||||
|
proofTestFileFlag,
|
||||||
traceTestInvalidOutputFlag,
|
traceTestInvalidOutputFlag,
|
||||||
|
proofTestInvalidOutputFlag,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testPatternFlag = &cli.StringFlag{
|
testPatternFlag = &cli.StringFlag{
|
||||||
|
|
@ -95,6 +97,7 @@ type testConfig struct {
|
||||||
historyTestFile string
|
historyTestFile string
|
||||||
historyPruneBlock *uint64
|
historyPruneBlock *uint64
|
||||||
traceTestFile string
|
traceTestFile string
|
||||||
|
proofTestFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
var errPrunedHistory = errors.New("attempt to access pruned history")
|
var errPrunedHistory = errors.New("attempt to access pruned history")
|
||||||
|
|
@ -145,6 +148,12 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||||
} else {
|
} else {
|
||||||
cfg.traceTestFile = "queries/trace_mainnet.json"
|
cfg.traceTestFile = "queries/trace_mainnet.json"
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(proofTestFileFlag.Name) {
|
||||||
|
cfg.proofTestFile = ctx.String(proofTestFileFlag.Name)
|
||||||
|
} else {
|
||||||
|
cfg.proofTestFile = "queries/proof_mainnet.json"
|
||||||
|
}
|
||||||
|
|
||||||
cfg.historyPruneBlock = new(uint64)
|
cfg.historyPruneBlock = new(uint64)
|
||||||
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
*cfg.historyPruneBlock = history.PrunePoints[params.MainnetGenesisHash].BlockNumber
|
||||||
case ctx.Bool(testSepoliaFlag.Name):
|
case ctx.Bool(testSepoliaFlag.Name):
|
||||||
|
|
@ -164,6 +173,12 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||||
} else {
|
} else {
|
||||||
cfg.traceTestFile = "queries/trace_sepolia.json"
|
cfg.traceTestFile = "queries/trace_sepolia.json"
|
||||||
}
|
}
|
||||||
|
if ctx.IsSet(proofTestFileFlag.Name) {
|
||||||
|
cfg.proofTestFile = ctx.String(proofTestFileFlag.Name)
|
||||||
|
} else {
|
||||||
|
cfg.proofTestFile = "queries/proof_sepolia.json"
|
||||||
|
}
|
||||||
|
|
||||||
cfg.historyPruneBlock = new(uint64)
|
cfg.historyPruneBlock = new(uint64)
|
||||||
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
*cfg.historyPruneBlock = history.PrunePoints[params.SepoliaGenesisHash].BlockNumber
|
||||||
default:
|
default:
|
||||||
|
|
@ -171,6 +186,7 @@ func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||||
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
cfg.filterQueryFile = ctx.String(filterQueryFileFlag.Name)
|
||||||
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
|
cfg.historyTestFile = ctx.String(historyTestFileFlag.Name)
|
||||||
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
|
cfg.traceTestFile = ctx.String(traceTestFileFlag.Name)
|
||||||
|
cfg.proofTestFile = ctx.String(proofTestFileFlag.Name)
|
||||||
}
|
}
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
@ -222,11 +238,13 @@ func runTestCmd(ctx *cli.Context) error {
|
||||||
filterSuite := newFilterTestSuite(cfg)
|
filterSuite := newFilterTestSuite(cfg)
|
||||||
historySuite := newHistoryTestSuite(cfg)
|
historySuite := newHistoryTestSuite(cfg)
|
||||||
traceSuite := newTraceTestSuite(cfg, ctx)
|
traceSuite := newTraceTestSuite(cfg, ctx)
|
||||||
|
proofSuite := newProofTestSuite(cfg, ctx)
|
||||||
|
|
||||||
// Filter test cases.
|
// Filter test cases.
|
||||||
tests := filterSuite.allTests()
|
tests := filterSuite.allTests()
|
||||||
tests = append(tests, historySuite.allTests()...)
|
tests = append(tests, historySuite.allTests()...)
|
||||||
tests = append(tests, traceSuite.allTests()...)
|
tests = append(tests, traceSuite.allTests()...)
|
||||||
|
tests = append(tests, proofSuite.allTests()...)
|
||||||
|
|
||||||
utests := filterTests(tests, ctx.String(testPatternFlag.Name), func(t workloadTest) bool {
|
utests := filterTests(tests, ctx.String(testPatternFlag.Name), func(t workloadTest) bool {
|
||||||
if t.Slow && !ctx.Bool(testSlowFlag.Name) {
|
if t.Slow && !ctx.Bool(testSlowFlag.Name) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ var (
|
||||||
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
|
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
|
||||||
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
|
||||||
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
|
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
|
||||||
|
codeReadTimer = metrics.NewRegisteredResettingTimer("chain/code/reads", nil)
|
||||||
|
|
||||||
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
|
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
|
||||||
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
|
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
|
||||||
|
|
@ -88,6 +89,7 @@ var (
|
||||||
|
|
||||||
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
|
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
|
||||||
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
|
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
|
||||||
|
codeReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/code/single/reads", nil)
|
||||||
|
|
||||||
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
|
snapshotCommitTimer = metrics.NewRegisteredResettingTimer("chain/snapshot/commits", nil)
|
||||||
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
|
triedbCommitTimer = metrics.NewRegisteredResettingTimer("chain/triedb/commits", nil)
|
||||||
|
|
@ -309,6 +311,7 @@ type BlockChain struct {
|
||||||
chainHeadFeed event.Feed
|
chainHeadFeed event.Feed
|
||||||
logsFeed event.Feed
|
logsFeed event.Feed
|
||||||
blockProcFeed event.Feed
|
blockProcFeed event.Feed
|
||||||
|
newPayloadFeed event.Feed // Feed for engine API newPayload events
|
||||||
blockProcCounter int32
|
blockProcCounter int32
|
||||||
scope event.SubscriptionScope
|
scope event.SubscriptionScope
|
||||||
genesisBlock *types.Block
|
genesisBlock *types.Block
|
||||||
|
|
@ -951,7 +954,8 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ
|
||||||
// Recover if the target state if it's not available yet.
|
// Recover if the target state if it's not available yet.
|
||||||
if !bc.HasState(head.Root) {
|
if !bc.HasState(head.Root) {
|
||||||
if err := bc.triedb.Recover(head.Root); err != nil {
|
if err := bc.triedb.Recover(head.Root); err != nil {
|
||||||
log.Crit("Failed to rollback state", "err", err)
|
log.Error("Failed to rollback state, resetting to genesis", "err", err)
|
||||||
|
return bc.genesisBlock.Header(), rootNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash())
|
log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash())
|
||||||
|
|
@ -1102,25 +1106,60 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
||||||
bc.txLookupCache.Purge()
|
bc.txLookupCache.Purge()
|
||||||
|
|
||||||
// Clear safe block, finalized block if needed
|
// Clear safe block, finalized block if needed
|
||||||
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
|
headBlock := bc.CurrentBlock()
|
||||||
|
if safe := bc.CurrentSafeBlock(); safe != nil && headBlock.Number.Uint64() < safe.Number.Uint64() {
|
||||||
log.Warn("SetHead invalidated safe block")
|
log.Warn("SetHead invalidated safe block")
|
||||||
bc.SetSafe(nil)
|
bc.SetSafe(nil)
|
||||||
}
|
}
|
||||||
if finalized := bc.CurrentFinalBlock(); finalized != nil && head < finalized.Number.Uint64() {
|
if finalized := bc.CurrentFinalBlock(); finalized != nil && headBlock.Number.Uint64() < finalized.Number.Uint64() {
|
||||||
log.Error("SetHead invalidated finalized block")
|
log.Error("SetHead invalidated finalized block")
|
||||||
bc.SetFinalized(nil)
|
bc.SetFinalized(nil)
|
||||||
}
|
}
|
||||||
return rootNumber, bc.loadLastState()
|
return rootNumber, bc.loadLastState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SnapSyncCommitHead sets the current head block to the one defined by the hash
|
// SnapSyncStart disables the underlying databases (such as the trie DB and the
|
||||||
// irrelevant what the chain contents were prior.
|
// optional state snapshot) to prevent potential concurrent mutations between
|
||||||
func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
|
// snap sync and other chain operations.
|
||||||
|
func (bc *BlockChain) SnapSyncStart() error {
|
||||||
|
if !bc.chainmu.TryLock() {
|
||||||
|
return errChainStopped
|
||||||
|
}
|
||||||
|
defer bc.chainmu.Unlock()
|
||||||
|
|
||||||
|
// Snap sync will directly modify the persistent state, making the entire
|
||||||
|
// trie database unusable until the state is fully synced. To prevent any
|
||||||
|
// subsequent state reads, explicitly disable the trie database and state
|
||||||
|
// syncer is responsible to address and correct any state missing.
|
||||||
|
if bc.TrieDB().Scheme() == rawdb.PathScheme {
|
||||||
|
if err := bc.TrieDB().Disable(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Snap sync uses the snapshot namespace to store potentially flaky data until
|
||||||
|
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
|
||||||
|
// time to prevent access.
|
||||||
|
if snapshots := bc.Snapshots(); snapshots != nil { // Only nil in tests
|
||||||
|
snapshots.Disable()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapSyncComplete sets the current head block to the block identified by the
|
||||||
|
// given hash, regardless of the chain contents prior to snap sync. It is
|
||||||
|
// invoked once snap sync completes and assumes that SnapSyncStart was called
|
||||||
|
// previously.
|
||||||
|
func (bc *BlockChain) SnapSyncComplete(hash common.Hash) error {
|
||||||
// Make sure that both the block as well at its state trie exists
|
// Make sure that both the block as well at its state trie exists
|
||||||
block := bc.GetBlockByHash(hash)
|
block := bc.GetBlockByHash(hash)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return fmt.Errorf("non existent block [%x..]", hash[:4])
|
return fmt.Errorf("non existent block [%x..]", hash[:4])
|
||||||
}
|
}
|
||||||
|
if !bc.chainmu.TryLock() {
|
||||||
|
return errChainStopped
|
||||||
|
}
|
||||||
|
defer bc.chainmu.Unlock()
|
||||||
|
|
||||||
// Reset the trie database with the fresh snap synced state.
|
// Reset the trie database with the fresh snap synced state.
|
||||||
root := block.Root()
|
root := block.Root()
|
||||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||||
|
|
@ -1131,19 +1170,16 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
|
||||||
if !bc.HasState(root) {
|
if !bc.HasState(root) {
|
||||||
return fmt.Errorf("non existent state [%x..]", root[:4])
|
return fmt.Errorf("non existent state [%x..]", root[:4])
|
||||||
}
|
}
|
||||||
// If all checks out, manually set the head block.
|
|
||||||
if !bc.chainmu.TryLock() {
|
|
||||||
return errChainStopped
|
|
||||||
}
|
|
||||||
bc.currentBlock.Store(block.Header())
|
|
||||||
headBlockGauge.Update(int64(block.NumberU64()))
|
|
||||||
bc.chainmu.Unlock()
|
|
||||||
|
|
||||||
// Destroy any existing state snapshot and regenerate it in the background,
|
// Destroy any existing state snapshot and regenerate it in the background,
|
||||||
// also resuming the normal maintenance of any previously paused snapshot.
|
// also resuming the normal maintenance of any previously paused snapshot.
|
||||||
if bc.snaps != nil {
|
if bc.snaps != nil {
|
||||||
bc.snaps.Rebuild(root)
|
bc.snaps.Rebuild(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If all checks out, manually set the head block.
|
||||||
|
bc.currentBlock.Store(block.Header())
|
||||||
|
headBlockGauge.Update(int64(block.NumberU64()))
|
||||||
|
|
||||||
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
|
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1602,13 +1638,17 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||||
//
|
//
|
||||||
// Note all the components of block(hash->number map, header, body, receipts)
|
// Note all the components of block(hash->number map, header, body, receipts)
|
||||||
// should be written atomically. BlockBatch is used for containing all components.
|
// should be written atomically. BlockBatch is used for containing all components.
|
||||||
blockBatch := bc.db.NewBatch()
|
var (
|
||||||
rawdb.WriteBlock(blockBatch, block)
|
batch = bc.db.NewBatch()
|
||||||
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
start = time.Now()
|
||||||
rawdb.WritePreimages(blockBatch, statedb.Preimages())
|
)
|
||||||
if err := blockBatch.Write(); err != nil {
|
rawdb.WriteBlock(batch, block)
|
||||||
|
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
|
||||||
|
rawdb.WritePreimages(batch, statedb.Preimages())
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write block into disk", "err", err)
|
log.Crit("Failed to write block into disk", "err", err)
|
||||||
}
|
}
|
||||||
|
log.Debug("Committed block data", "size", common.StorageSize(batch.ValueSize()), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
|
|
@ -2167,6 +2207,7 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation)
|
stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation)
|
||||||
stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation)
|
stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation)
|
||||||
stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation)
|
stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation)
|
||||||
|
stats.CodeReads = statedb.CodeReads
|
||||||
|
|
||||||
stats.AccountLoaded = statedb.AccountLoaded
|
stats.AccountLoaded = statedb.AccountLoaded
|
||||||
stats.AccountUpdated = statedb.AccountUpdated
|
stats.AccountUpdated = statedb.AccountUpdated
|
||||||
|
|
@ -2174,8 +2215,9 @@ func (bc *BlockChain) ProcessBlock(parentRoot common.Hash, block *types.Block, s
|
||||||
stats.StorageLoaded = statedb.StorageLoaded
|
stats.StorageLoaded = statedb.StorageLoaded
|
||||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||||
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
stats.StorageDeleted = int(statedb.StorageDeleted.Load())
|
||||||
|
stats.CodeLoaded = statedb.CodeLoaded
|
||||||
|
|
||||||
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads) // The time spent on EVM processing
|
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
||||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||||
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -522,3 +522,13 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
|
||||||
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
|
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
|
||||||
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
|
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeNewPayloadEvent registers a subscription for NewPayloadEvent.
|
||||||
|
func (bc *BlockChain) SubscribeNewPayloadEvent(ch chan<- NewPayloadEvent) event.Subscription {
|
||||||
|
return bc.scope.Track(bc.newPayloadFeed.Subscribe(ch))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendNewPayloadEvent sends a NewPayloadEvent to subscribers.
|
||||||
|
func (bc *BlockChain) SendNewPayloadEvent(ev NewPayloadEvent) {
|
||||||
|
bc.newPayloadFeed.Send(ev)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ type ExecuteStats struct {
|
||||||
AccountCommits time.Duration // Time spent on the account trie commit
|
AccountCommits time.Duration // Time spent on the account trie commit
|
||||||
StorageUpdates time.Duration // Time spent on the storage trie update
|
StorageUpdates time.Duration // Time spent on the storage trie update
|
||||||
StorageCommits time.Duration // Time spent on the storage trie commit
|
StorageCommits time.Duration // Time spent on the storage trie commit
|
||||||
|
CodeReads time.Duration // Time spent on the contract code read
|
||||||
|
|
||||||
AccountLoaded int // Number of accounts loaded
|
AccountLoaded int // Number of accounts loaded
|
||||||
AccountUpdated int // Number of accounts updated
|
AccountUpdated int // Number of accounts updated
|
||||||
|
|
@ -44,6 +45,7 @@ type ExecuteStats struct {
|
||||||
StorageLoaded int // Number of storage slots loaded
|
StorageLoaded int // Number of storage slots loaded
|
||||||
StorageUpdated int // Number of storage slots updated
|
StorageUpdated int // Number of storage slots updated
|
||||||
StorageDeleted int // Number of storage slots deleted
|
StorageDeleted int // Number of storage slots deleted
|
||||||
|
CodeLoaded int // Number of contract code loaded
|
||||||
|
|
||||||
Execution time.Duration // Time spent on the EVM execution
|
Execution time.Duration // Time spent on the EVM execution
|
||||||
Validation time.Duration // Time spent on the block validation
|
Validation time.Duration // Time spent on the block validation
|
||||||
|
|
@ -61,19 +63,21 @@ type ExecuteStats struct {
|
||||||
|
|
||||||
// reportMetrics uploads execution statistics to the metrics system.
|
// reportMetrics uploads execution statistics to the metrics system.
|
||||||
func (s *ExecuteStats) reportMetrics() {
|
func (s *ExecuteStats) reportMetrics() {
|
||||||
accountReadTimer.Update(s.AccountReads) // Account reads are complete(in processing)
|
|
||||||
storageReadTimer.Update(s.StorageReads) // Storage reads are complete(in processing)
|
|
||||||
if s.AccountLoaded != 0 {
|
if s.AccountLoaded != 0 {
|
||||||
|
accountReadTimer.Update(s.AccountReads)
|
||||||
accountReadSingleTimer.Update(s.AccountReads / time.Duration(s.AccountLoaded))
|
accountReadSingleTimer.Update(s.AccountReads / time.Duration(s.AccountLoaded))
|
||||||
}
|
}
|
||||||
if s.StorageLoaded != 0 {
|
if s.StorageLoaded != 0 {
|
||||||
|
storageReadTimer.Update(s.StorageReads)
|
||||||
storageReadSingleTimer.Update(s.StorageReads / time.Duration(s.StorageLoaded))
|
storageReadSingleTimer.Update(s.StorageReads / time.Duration(s.StorageLoaded))
|
||||||
}
|
}
|
||||||
|
if s.CodeLoaded != 0 {
|
||||||
|
codeReadTimer.Update(s.CodeReads)
|
||||||
|
codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded))
|
||||||
|
}
|
||||||
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation)
|
||||||
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
|
storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation)
|
||||||
accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation)
|
accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation)
|
||||||
|
|
||||||
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
|
accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them
|
||||||
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
|
storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them
|
||||||
|
|
||||||
|
|
@ -111,24 +115,47 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat
|
||||||
Block: %v (%#x) txs: %d, mgasps: %.2f, elapsed: %v
|
Block: %v (%#x) txs: %d, mgasps: %.2f, elapsed: %v
|
||||||
|
|
||||||
EVM execution: %v
|
EVM execution: %v
|
||||||
|
|
||||||
Validation: %v
|
Validation: %v
|
||||||
Account read: %v(%d)
|
Account hash: %v
|
||||||
Storage read: %v(%d)
|
Storage hash: %v
|
||||||
Account hash: %v
|
|
||||||
Storage hash: %v
|
State read: %v
|
||||||
DB commit: %v
|
Account read: %v(%d)
|
||||||
Block write: %v
|
Storage read: %v(%d)
|
||||||
|
Code read: %v(%d)
|
||||||
|
|
||||||
|
State write: %v
|
||||||
|
Trie commit: %v
|
||||||
|
State write: %v
|
||||||
|
Block write: %v
|
||||||
|
|
||||||
%s
|
%s
|
||||||
##############################
|
##############################
|
||||||
`, block.Number(), block.Hash(), len(block.Transactions()), s.MgasPerSecond, common.PrettyDuration(s.TotalTime),
|
`, block.Number(), block.Hash(), len(block.Transactions()), s.MgasPerSecond, common.PrettyDuration(s.TotalTime),
|
||||||
common.PrettyDuration(s.Execution), common.PrettyDuration(s.Validation+s.CrossValidation),
|
// EVM execution
|
||||||
|
common.PrettyDuration(s.Execution),
|
||||||
|
|
||||||
|
// Block validation
|
||||||
|
common.PrettyDuration(s.Validation+s.CrossValidation+s.AccountHashes+s.AccountUpdates+s.StorageUpdates),
|
||||||
|
common.PrettyDuration(s.AccountHashes+s.AccountUpdates),
|
||||||
|
common.PrettyDuration(s.StorageUpdates),
|
||||||
|
|
||||||
|
// State read
|
||||||
|
common.PrettyDuration(s.AccountReads+s.StorageReads+s.CodeReads),
|
||||||
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
|
common.PrettyDuration(s.AccountReads), s.AccountLoaded,
|
||||||
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
|
common.PrettyDuration(s.StorageReads), s.StorageLoaded,
|
||||||
common.PrettyDuration(s.AccountHashes+s.AccountCommits+s.AccountUpdates),
|
common.PrettyDuration(s.CodeReads), s.CodeLoaded,
|
||||||
common.PrettyDuration(s.StorageCommits+s.StorageUpdates),
|
|
||||||
common.PrettyDuration(s.TrieDBCommit+s.SnapshotCommit), common.PrettyDuration(s.BlockWrite),
|
// State write
|
||||||
s.StateReadCacheStats)
|
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)+s.TrieDBCommit+s.SnapshotCommit+s.BlockWrite),
|
||||||
|
common.PrettyDuration(max(s.AccountCommits, s.StorageCommits)),
|
||||||
|
common.PrettyDuration(s.TrieDBCommit+s.SnapshotCommit),
|
||||||
|
common.PrettyDuration(s.BlockWrite),
|
||||||
|
|
||||||
|
// cache statistics
|
||||||
|
s.StateReadCacheStats,
|
||||||
|
)
|
||||||
for _, line := range strings.Split(msg, "\n") {
|
for _, line := range strings.Split(msg, "\n") {
|
||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -4515,3 +4515,45 @@ func TestGetCanonicalReceipt(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSetHeadBeyondRootFinalizedBug tests the issue where the finalized block
|
||||||
|
// is not cleared when rewinding past it using setHeadBeyondRoot.
|
||||||
|
func TestSetHeadBeyondRootFinalizedBug(t *testing.T) {
|
||||||
|
// Create a clean blockchain with 100 blocks using PathScheme (PBSS)
|
||||||
|
_, _, blockchain, err := newCanonical(ethash.NewFaker(), 100, true, rawdb.PathScheme)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create pristine chain: %v", err)
|
||||||
|
}
|
||||||
|
defer blockchain.Stop()
|
||||||
|
|
||||||
|
// Set the "Finalized" marker to the current Head (Block 100)
|
||||||
|
headBlock := blockchain.CurrentBlock()
|
||||||
|
if headBlock.Number.Uint64() != 100 {
|
||||||
|
t.Fatalf("Setup failed: expected head 100, got %d", headBlock.Number.Uint64())
|
||||||
|
}
|
||||||
|
blockchain.SetFinalized(headBlock)
|
||||||
|
|
||||||
|
// Verify setup
|
||||||
|
if blockchain.CurrentFinalBlock().Number.Uint64() != 100 {
|
||||||
|
t.Fatalf("Setup failed: Finalized block should be 100")
|
||||||
|
}
|
||||||
|
targetBlock := blockchain.GetBlockByNumber(50)
|
||||||
|
|
||||||
|
// Call setHeadBeyondRoot with:
|
||||||
|
// head = 100
|
||||||
|
// repair = true
|
||||||
|
if _, err := blockchain.setHeadBeyondRoot(100, 0, targetBlock.Root(), true); err != nil {
|
||||||
|
t.Fatalf("Failed to rewind: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFinal := blockchain.CurrentFinalBlock()
|
||||||
|
currentHead := blockchain.CurrentBlock().Number.Uint64()
|
||||||
|
|
||||||
|
// The previous finalized block (100) is now invalid because we rewound to 50.
|
||||||
|
// The function should have cleared the finalized marker (set to nil).
|
||||||
|
if currentFinal != nil && currentFinal.Number.Uint64() > currentHead {
|
||||||
|
t.Errorf("Chain Head: %d , Finalized Block: %d , Finalized block was >= head block.",
|
||||||
|
currentHead,
|
||||||
|
currentFinal.Number.Uint64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -35,3 +38,10 @@ type ChainEvent struct {
|
||||||
type ChainHeadEvent struct {
|
type ChainHeadEvent struct {
|
||||||
Header *types.Header
|
Header *types.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPayloadEvent is posted when engine_newPayloadVX processes a block.
|
||||||
|
type NewPayloadEvent struct {
|
||||||
|
Hash common.Hash
|
||||||
|
Number uint64
|
||||||
|
ProcessingTime time.Duration
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle boo
|
||||||
// Initialize the first transition state, with the "ended"
|
// Initialize the first transition state, with the "ended"
|
||||||
// field set to true if the database was created
|
// field set to true if the database was created
|
||||||
// as a verkle database.
|
// as a verkle database.
|
||||||
log.Debug("no transition state found, starting fresh", "is verkle", db)
|
log.Debug("no transition state found, starting fresh", "verkle", isVerkle)
|
||||||
|
|
||||||
// Start with a fresh state
|
// Start with a fresh state
|
||||||
ts = &TransitionState{Ended: isVerkle}
|
ts = &TransitionState{Ended: isVerkle}
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,8 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s
|
||||||
path, tables = resolveChainFreezerDir(ancient), chainFreezerTableConfigs
|
path, tables = resolveChainFreezerDir(ancient), chainFreezerTableConfigs
|
||||||
case MerkleStateFreezerName, VerkleStateFreezerName:
|
case MerkleStateFreezerName, VerkleStateFreezerName:
|
||||||
path, tables = filepath.Join(ancient, freezerName), stateFreezerTableConfigs
|
path, tables = filepath.Join(ancient, freezerName), stateFreezerTableConfigs
|
||||||
|
case MerkleTrienodeFreezerName, VerkleTrienodeFreezerName:
|
||||||
|
path, tables = filepath.Join(ancient, freezerName), trienodeFreezerTableConfigs
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,6 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash {
|
||||||
|
|
||||||
storageList := slices.SortedFunc(maps.Keys(dl.storageData[accountHash]), common.Hash.Cmp)
|
storageList := slices.SortedFunc(maps.Keys(dl.storageData[accountHash]), common.Hash.Cmp)
|
||||||
dl.storageList[accountHash] = storageList
|
dl.storageList[accountHash] = storageList
|
||||||
dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength)
|
dl.memory += uint64(len(storageList)*common.HashLength + common.HashLength)
|
||||||
return storageList
|
return storageList
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,39 @@ func TestInsertAndMerge(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestStorageListMemoryAccounting ensures that StorageList increases dl.memory
|
||||||
|
// proportionally to the number of storage slots in the requested account and
|
||||||
|
// does not change memory usage on repeated calls for the same account.
|
||||||
|
func TestStorageListMemoryAccounting(t *testing.T) {
|
||||||
|
parent := newDiffLayer(emptyLayer(), common.Hash{}, nil, nil)
|
||||||
|
account := common.HexToHash("0x01")
|
||||||
|
|
||||||
|
slots := make(map[common.Hash][]byte)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
slots[randomHash()] = []byte{0x01}
|
||||||
|
}
|
||||||
|
storage := map[common.Hash]map[common.Hash][]byte{
|
||||||
|
account: slots,
|
||||||
|
}
|
||||||
|
dl := newDiffLayer(parent, common.Hash{}, nil, storage)
|
||||||
|
|
||||||
|
before := dl.memory
|
||||||
|
list := dl.StorageList(account)
|
||||||
|
if have, want := len(list), len(slots); have != want {
|
||||||
|
t.Fatalf("StorageList length mismatch: have %d, want %d", have, want)
|
||||||
|
}
|
||||||
|
expectedDelta := uint64(len(list)*common.HashLength + common.HashLength)
|
||||||
|
if have, want := dl.memory-before, expectedDelta; have != want {
|
||||||
|
t.Fatalf("StorageList memory delta mismatch: have %d, want %d", have, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
before = dl.memory
|
||||||
|
_ = dl.StorageList(account)
|
||||||
|
if dl.memory != before {
|
||||||
|
t.Fatalf("StorageList changed memory on cached call: have %d, want %d", dl.memory, before)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func emptyLayer() *diskLayer {
|
func emptyLayer() *diskLayer {
|
||||||
return &diskLayer{
|
return &diskLayer{
|
||||||
diskdb: memorydb.New(),
|
diskdb: memorydb.New(),
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
@ -531,6 +532,11 @@ func (s *stateObject) Code() []byte {
|
||||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
defer func(start time.Time) {
|
||||||
|
s.db.CodeLoaded += 1
|
||||||
|
s.db.CodeReads += time.Since(start)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
code, err := s.db.reader.Code(s.address, common.BytesToHash(s.CodeHash()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
|
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
|
||||||
|
|
@ -552,6 +558,11 @@ func (s *stateObject) CodeSize() int {
|
||||||
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
defer func(start time.Time) {
|
||||||
|
s.db.CodeLoaded += 1
|
||||||
|
s.db.CodeReads += time.Since(start)
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
size, err := s.db.reader.CodeSize(s.address, common.BytesToHash(s.CodeHash()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
|
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
@ -151,6 +150,7 @@ type StateDB struct {
|
||||||
StorageCommits time.Duration
|
StorageCommits time.Duration
|
||||||
SnapshotCommits time.Duration
|
SnapshotCommits time.Duration
|
||||||
TrieDBCommits time.Duration
|
TrieDBCommits time.Duration
|
||||||
|
CodeReads time.Duration
|
||||||
|
|
||||||
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
||||||
AccountUpdated int // Number of accounts updated during the state transition
|
AccountUpdated int // Number of accounts updated during the state transition
|
||||||
|
|
@ -158,6 +158,7 @@ type StateDB struct {
|
||||||
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
||||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||||
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
|
||||||
|
CodeLoaded int // Number of contract code loaded during the state transition
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// New creates a new state from a given trie.
|
||||||
|
|
@ -186,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
|
||||||
}
|
}
|
||||||
|
|
@ -1493,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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ func TestHooks(t *testing.T) {
|
||||||
|
|
||||||
for i, want := range wants {
|
for i, want := range wants {
|
||||||
if have := result[i]; have != want {
|
if have := result[i]; have != want {
|
||||||
t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want)
|
t.Fatalf("error event %d\nhave: %v\nwant: %v", i, have, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +165,7 @@ func TestHooks_OnCodeChangeV2(t *testing.T) {
|
||||||
|
|
||||||
for i, want := range wants {
|
for i, want := range wants {
|
||||||
if have := result[i]; have != want {
|
if have := result[i]; have != want {
|
||||||
t.Fatalf("error event %d, have\n%v\nwant%v\n", i, have, want)
|
t.Fatalf("error event %d\nhave: %v\nwant: %v", i, have, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ type stateUpdate struct {
|
||||||
storagesOrigin map[common.Address]map[common.Hash][]byte
|
storagesOrigin map[common.Address]map[common.Hash][]byte
|
||||||
rawStorageKey bool
|
rawStorageKey bool
|
||||||
|
|
||||||
codes map[common.Address]contractCode // codes contains the set of dirty codes
|
codes map[common.Address]*contractCode // codes contains the set of dirty codes
|
||||||
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty returns a flag indicating the state transition is empty or not.
|
// empty returns a flag indicating the state transition is empty or not.
|
||||||
|
|
@ -104,7 +104,7 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
|
||||||
accountsOrigin = make(map[common.Address][]byte)
|
accountsOrigin = make(map[common.Address][]byte)
|
||||||
storages = make(map[common.Hash]map[common.Hash][]byte)
|
storages = make(map[common.Hash]map[common.Hash][]byte)
|
||||||
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||||
codes = make(map[common.Address]contractCode)
|
codes = make(map[common.Address]*contractCode)
|
||||||
)
|
)
|
||||||
// Since some accounts might be destroyed and recreated within the same
|
// Since some accounts might be destroyed and recreated within the same
|
||||||
// block, deletions must be aggregated first.
|
// block, deletions must be aggregated first.
|
||||||
|
|
@ -126,7 +126,7 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
|
||||||
// Aggregate dirty contract codes if they are available.
|
// Aggregate dirty contract codes if they are available.
|
||||||
addr := op.address
|
addr := op.address
|
||||||
if op.code != nil {
|
if op.code != nil {
|
||||||
codes[addr] = *op.code
|
codes[addr] = op.code
|
||||||
}
|
}
|
||||||
accounts[addrHash] = op.data
|
accounts[addrHash] = op.data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,10 @@ import (
|
||||||
"container/heap"
|
"container/heap"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -381,7 +379,6 @@ func New(config Config, chain BlockChain, hasPendingAuth func(common.Address) bo
|
||||||
hasPendingAuth: hasPendingAuth,
|
hasPendingAuth: hasPendingAuth,
|
||||||
signer: types.LatestSigner(chain.Config()),
|
signer: types.LatestSigner(chain.Config()),
|
||||||
chain: chain,
|
chain: chain,
|
||||||
cQueue: newConversionQueue(), // Deprecate it after the osaka fork
|
|
||||||
lookup: newLookup(),
|
lookup: newLookup(),
|
||||||
index: make(map[common.Address][]*blobTxMeta),
|
index: make(map[common.Address][]*blobTxMeta),
|
||||||
spent: make(map[common.Address]*uint256.Int),
|
spent: make(map[common.Address]*uint256.Int),
|
||||||
|
|
@ -434,7 +431,7 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
||||||
p.state = state
|
p.state = state
|
||||||
|
|
||||||
// Create new slotter for pre-Osaka blob configuration.
|
// Create new slotter for pre-Osaka blob configuration.
|
||||||
slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(p.chain.Config()))
|
slotter := newSlotter(params.BlobTxMaxBlobs)
|
||||||
|
|
||||||
// See if we need to migrate the queue blob store after fusaka
|
// See if we need to migrate the queue blob store after fusaka
|
||||||
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
|
slotter, err = tryMigrate(p.chain.Config(), slotter, queuedir)
|
||||||
|
|
@ -505,9 +502,6 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
||||||
|
|
||||||
// Close closes down the underlying persistent store.
|
// Close closes down the underlying persistent store.
|
||||||
func (p *BlobPool) Close() error {
|
func (p *BlobPool) Close() error {
|
||||||
// Terminate the conversion queue
|
|
||||||
p.cQueue.close()
|
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
if p.limbo != nil { // Close might be invoked due to error in constructor, before p,limbo is set
|
if p.limbo != nil { // Close might be invoked due to error in constructor, before p,limbo is set
|
||||||
if err := p.limbo.Close(); err != nil {
|
if err := p.limbo.Close(); err != nil {
|
||||||
|
|
@ -908,172 +902,6 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
||||||
basefeeGauge.Update(int64(basefee.Uint64()))
|
basefeeGauge.Update(int64(basefee.Uint64()))
|
||||||
blobfeeGauge.Update(int64(blobfee.Uint64()))
|
blobfeeGauge.Update(int64(blobfee.Uint64()))
|
||||||
p.updateStorageMetrics()
|
p.updateStorageMetrics()
|
||||||
|
|
||||||
// Perform the conversion logic at the fork boundary
|
|
||||||
if !p.chain.Config().IsOsaka(oldHead.Number, oldHead.Time) && p.chain.Config().IsOsaka(newHead.Number, newHead.Time) {
|
|
||||||
// Deep copy all indexed transaction metadata.
|
|
||||||
var (
|
|
||||||
ids = make(map[common.Address]map[uint64]uint64)
|
|
||||||
txs = make(map[common.Address]map[uint64]common.Hash)
|
|
||||||
)
|
|
||||||
for sender, list := range p.index {
|
|
||||||
ids[sender] = make(map[uint64]uint64)
|
|
||||||
txs[sender] = make(map[uint64]common.Hash)
|
|
||||||
for _, m := range list {
|
|
||||||
ids[sender][m.nonce] = m.id
|
|
||||||
txs[sender][m.nonce] = m.hash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Initiate the background conversion thread.
|
|
||||||
p.cQueue.launchBillyConversion(func() {
|
|
||||||
p.convertLegacySidecars(ids, txs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compareAndSwap checks if the specified transaction is still tracked in the pool
|
|
||||||
// and replace the metadata accordingly. It should only be used in the fork boundary
|
|
||||||
// bulk conversion. If it fails for some reason, the subsequent txs won't be dropped
|
|
||||||
// for simplicity which we assume it's very likely to happen.
|
|
||||||
//
|
|
||||||
// The returned flag indicates whether the replacement succeeded.
|
|
||||||
func (p *BlobPool) compareAndSwap(address common.Address, hash common.Hash, blob []byte, oldID uint64, oldStorageSize uint32) bool {
|
|
||||||
p.lock.Lock()
|
|
||||||
defer p.lock.Unlock()
|
|
||||||
|
|
||||||
newId, err := p.store.Put(blob)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to store transaction", "hash", hash, "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
newSize := uint64(len(blob))
|
|
||||||
newStorageSize := p.store.Size(newId)
|
|
||||||
|
|
||||||
// Terminate the procedure if the transaction was already evicted. The
|
|
||||||
// newly added blob should be removed before return.
|
|
||||||
if !p.lookup.update(hash, newId, newSize) {
|
|
||||||
if derr := p.store.Delete(newId); derr != nil {
|
|
||||||
log.Error("Failed to delete the dangling blob tx", "err", derr)
|
|
||||||
} else {
|
|
||||||
log.Warn("Deleted the dangling blob tx", "id", newId)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Update the metadata of blob transaction
|
|
||||||
for _, meta := range p.index[address] {
|
|
||||||
if meta.hash == hash {
|
|
||||||
meta.id = newId
|
|
||||||
meta.version = types.BlobSidecarVersion1
|
|
||||||
meta.storageSize = newStorageSize
|
|
||||||
meta.size = newSize
|
|
||||||
|
|
||||||
p.stored += uint64(newStorageSize)
|
|
||||||
p.stored -= uint64(oldStorageSize)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := p.store.Delete(oldID); err != nil {
|
|
||||||
log.Error("Failed to delete the legacy transaction", "hash", hash, "id", oldID, "err", err)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertLegacySidecar fetches transaction data from the store, performs an
|
|
||||||
// on-the-fly conversion. This function is intended for use only during the
|
|
||||||
// Osaka fork transition period.
|
|
||||||
//
|
|
||||||
// The returned flag indicates whether the replacement succeeds or not.
|
|
||||||
func (p *BlobPool) convertLegacySidecar(sender common.Address, hash common.Hash, id uint64) bool {
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
// Retrieves the legacy blob transaction from the underlying store with
|
|
||||||
// read lock held, preventing any potential data race around the slot
|
|
||||||
// specified by the id.
|
|
||||||
p.lock.RLock()
|
|
||||||
data, err := p.store.Get(id)
|
|
||||||
if err != nil {
|
|
||||||
p.lock.RUnlock()
|
|
||||||
// The transaction may have been evicted simultaneously, safe to skip conversion.
|
|
||||||
log.Debug("Blob transaction is missing", "hash", hash, "id", id, "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
oldStorageSize := p.store.Size(id)
|
|
||||||
p.lock.RUnlock()
|
|
||||||
|
|
||||||
// Decode the transaction, the failure is not expected and report the error
|
|
||||||
// loudly if possible. If the blob transaction in this slot is corrupted,
|
|
||||||
// leave it in the store, it will be dropped during the next pool
|
|
||||||
// initialization.
|
|
||||||
var tx types.Transaction
|
|
||||||
if err = rlp.DecodeBytes(data, &tx); err != nil {
|
|
||||||
log.Error("Blob transaction is corrupted", "hash", hash, "id", id, "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip conversion if the transaction does not match the expected hash, or if it was
|
|
||||||
// already converted. This can occur if the original transaction was evicted from the
|
|
||||||
// pool and the slot was reused by a new one.
|
|
||||||
if tx.Hash() != hash {
|
|
||||||
log.Warn("Blob transaction was replaced", "hash", hash, "id", id, "stored", tx.Hash())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
sc := tx.BlobTxSidecar()
|
|
||||||
if sc.Version >= types.BlobSidecarVersion1 {
|
|
||||||
log.Debug("Skipping conversion of blob tx", "hash", hash, "id", id)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the sidecar conversion, the failure is not expected and report the error
|
|
||||||
// loudly if possible.
|
|
||||||
if err := tx.BlobTxSidecar().ToV1(); err != nil {
|
|
||||||
log.Error("Failed to convert blob transaction", "hash", hash, "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode the converted transaction, the failure is not expected and report
|
|
||||||
// the error loudly if possible.
|
|
||||||
blob, err := rlp.EncodeToBytes(&tx)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to encode blob transaction", "hash", tx.Hash(), "err", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the legacy blob transaction with the converted format.
|
|
||||||
if !p.compareAndSwap(sender, hash, blob, id, oldStorageSize) {
|
|
||||||
log.Error("Failed to replace the legacy transaction", "hash", hash)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
log.Debug("Converted legacy blob transaction", "hash", hash, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertLegacySidecars converts all given transactions to sidecar version 1.
|
|
||||||
//
|
|
||||||
// If any of them fails to be converted, the subsequent transactions will still
|
|
||||||
// be processed, as we assume the failure is very unlikely to happen. If happens,
|
|
||||||
// these transactions will be stuck in the pool until eviction.
|
|
||||||
func (p *BlobPool) convertLegacySidecars(ids map[common.Address]map[uint64]uint64, txs map[common.Address]map[uint64]common.Hash) {
|
|
||||||
var (
|
|
||||||
start = time.Now()
|
|
||||||
success int
|
|
||||||
failure int
|
|
||||||
)
|
|
||||||
for addr, list := range txs {
|
|
||||||
// Transactions evicted from the pool must be contiguous, if in any case,
|
|
||||||
// the transactions are gapped with each other, they will be discarded.
|
|
||||||
nonces := slices.Collect(maps.Keys(list))
|
|
||||||
slices.Sort(nonces)
|
|
||||||
|
|
||||||
// Convert the txs with nonce order
|
|
||||||
for _, nonce := range nonces {
|
|
||||||
if p.convertLegacySidecar(addr, list[nonce], ids[addr][nonce]) {
|
|
||||||
success++
|
|
||||||
} else {
|
|
||||||
failure++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Info("Completed blob transaction conversion", "discarded", failure, "injected", success, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reorg assembles all the transactors and missing transactions between an old
|
// reorg assembles all the transactors and missing transactions between an old
|
||||||
|
|
@ -1557,8 +1385,8 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
||||||
//
|
//
|
||||||
// The version argument specifies the type of proofs to return, either the
|
// The version argument specifies the type of proofs to return, either the
|
||||||
// blob proofs (version 0) or the cell proofs (version 1). Proofs conversion is
|
// blob proofs (version 0) or the cell proofs (version 1). Proofs conversion is
|
||||||
// CPU intensive, so only done if explicitly requested with the convert flag.
|
// CPU intensive and prohibited in the blobpool explicitly.
|
||||||
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
|
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
|
||||||
var (
|
var (
|
||||||
blobs = make([]*kzg4844.Blob, len(vhashes))
|
blobs = make([]*kzg4844.Blob, len(vhashes))
|
||||||
commitments = make([]kzg4844.Commitment, len(vhashes))
|
commitments = make([]kzg4844.Commitment, len(vhashes))
|
||||||
|
|
@ -1609,7 +1437,7 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) (
|
||||||
}
|
}
|
||||||
// Mark hash as seen.
|
// Mark hash as seen.
|
||||||
filled[hash] = struct{}{}
|
filled[hash] = struct{}{}
|
||||||
if sidecar.Version != version && !convert {
|
if sidecar.Version != version {
|
||||||
// Skip blobs with incompatible version. Note we still track the blob hash
|
// Skip blobs with incompatible version. Note we still track the blob hash
|
||||||
// in `filled` here, ensuring that we do not resolve this tx another time.
|
// in `filled` here, ensuring that we do not resolve this tx another time.
|
||||||
continue
|
continue
|
||||||
|
|
@ -1618,29 +1446,13 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) (
|
||||||
var pf []kzg4844.Proof
|
var pf []kzg4844.Proof
|
||||||
switch version {
|
switch version {
|
||||||
case types.BlobSidecarVersion0:
|
case types.BlobSidecarVersion0:
|
||||||
if sidecar.Version == types.BlobSidecarVersion0 {
|
pf = []kzg4844.Proof{sidecar.Proofs[i]}
|
||||||
pf = []kzg4844.Proof{sidecar.Proofs[i]}
|
|
||||||
} else {
|
|
||||||
proof, err := kzg4844.ComputeBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
pf = []kzg4844.Proof{proof}
|
|
||||||
}
|
|
||||||
case types.BlobSidecarVersion1:
|
case types.BlobSidecarVersion1:
|
||||||
if sidecar.Version == types.BlobSidecarVersion0 {
|
cellProofs, err := sidecar.CellProofsAt(i)
|
||||||
cellProofs, err := kzg4844.ComputeCellProofs(&sidecar.Blobs[i])
|
if err != nil {
|
||||||
if err != nil {
|
return nil, nil, nil, err
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
pf = cellProofs
|
|
||||||
} else {
|
|
||||||
cellProofs, err := sidecar.CellProofsAt(i)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
pf = cellProofs
|
|
||||||
}
|
}
|
||||||
|
pf = cellProofs
|
||||||
}
|
}
|
||||||
for _, index := range list {
|
for _, index := range list {
|
||||||
blobs[index] = &sidecar.Blobs[i]
|
blobs[index] = &sidecar.Blobs[i]
|
||||||
|
|
@ -1667,56 +1479,15 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
|
||||||
return available
|
return available
|
||||||
}
|
}
|
||||||
|
|
||||||
// preCheck performs the static validation upon the provided tx and converts
|
|
||||||
// the legacy sidecars if Osaka fork has been activated with a short time window.
|
|
||||||
//
|
|
||||||
// This function is pure static and lock free.
|
|
||||||
func (p *BlobPool) preCheck(tx *types.Transaction) error {
|
|
||||||
var (
|
|
||||||
head = p.head.Load()
|
|
||||||
isOsaka = p.chain.Config().IsOsaka(head.Number, head.Time)
|
|
||||||
deadline time.Time
|
|
||||||
)
|
|
||||||
if isOsaka {
|
|
||||||
deadline = time.Unix(int64(*p.chain.Config().OsakaTime), 0).Add(conversionTimeWindow)
|
|
||||||
}
|
|
||||||
// Validate the transaction statically at first to avoid unnecessary
|
|
||||||
// conversion. This step doesn't require lock protection.
|
|
||||||
if err := p.ValidateTxBasics(tx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Before the Osaka fork, reject the blob txs with cell proofs
|
|
||||||
if !isOsaka {
|
|
||||||
if tx.BlobTxSidecar().Version == types.BlobSidecarVersion0 {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return errors.New("cell proof is not supported yet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// After the Osaka fork, reject the legacy blob txs if the conversion
|
|
||||||
// time window is passed.
|
|
||||||
if tx.BlobTxSidecar().Version == types.BlobSidecarVersion1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if head.Time > uint64(deadline.Unix()) {
|
|
||||||
return errors.New("legacy blob tx is not supported")
|
|
||||||
}
|
|
||||||
// Convert the legacy sidecar after Osaka fork. This could be a long
|
|
||||||
// procedure which takes a few seconds, even minutes if there is a long
|
|
||||||
// queue. Fortunately it will only block the routine of the source peer
|
|
||||||
// announcing the tx, without affecting other parts.
|
|
||||||
return p.cQueue.convert(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
// Add inserts a set of blob transactions into the pool if they pass validation (both
|
||||||
// consensus validity and pool restrictions).
|
// consensus validity and pool restrictions).
|
||||||
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||||
var (
|
var (
|
||||||
errs []error = make([]error, len(txs))
|
errs = make([]error, len(txs))
|
||||||
adds = make([]*types.Transaction, 0, len(txs))
|
adds = make([]*types.Transaction, 0, len(txs))
|
||||||
)
|
)
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
if errs[i] = p.preCheck(tx); errs[i] != nil {
|
if errs[i] = p.ValidateTxBasics(tx); errs[i] != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if errs[i] = p.add(tx); errs[i] == nil {
|
if errs[i] = p.add(tx); errs[i] == nil {
|
||||||
|
|
|
||||||
|
|
@ -92,10 +92,6 @@ type testBlockChain struct {
|
||||||
blockTime *uint64
|
blockTime *uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *testBlockChain) setHeadTime(time uint64) {
|
|
||||||
bc.blockTime = &time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bc *testBlockChain) Config() *params.ChainConfig {
|
func (bc *testBlockChain) Config() *params.ChainConfig {
|
||||||
return bc.config
|
return bc.config
|
||||||
}
|
}
|
||||||
|
|
@ -433,11 +429,11 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
||||||
hashes = append(hashes, tx.vhashes...)
|
hashes = append(hashes, tx.vhashes...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0, false)
|
blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1, false)
|
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -1329,7 +1325,7 @@ func TestBlobCountLimit(t *testing.T) {
|
||||||
|
|
||||||
// Check that first succeeds second fails.
|
// Check that first succeeds second fails.
|
||||||
if errs[0] != nil {
|
if errs[0] != nil {
|
||||||
t.Fatalf("expected tx with 7 blobs to succeed")
|
t.Fatalf("expected tx with 7 blobs to succeed, got %v", errs[0])
|
||||||
}
|
}
|
||||||
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
|
if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) {
|
||||||
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
|
t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1])
|
||||||
|
|
@ -1829,66 +1825,6 @@ func TestAdd(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that transactions with legacy sidecars are accepted within the
|
|
||||||
// conversion window but rejected after it has passed.
|
|
||||||
func TestAddLegacyBlobTx(t *testing.T) {
|
|
||||||
testAddLegacyBlobTx(t, true) // conversion window has not yet passed
|
|
||||||
testAddLegacyBlobTx(t, false) // conversion window passed
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAddLegacyBlobTx(t *testing.T, accept bool) {
|
|
||||||
var (
|
|
||||||
key1, _ = crypto.GenerateKey()
|
|
||||||
key2, _ = crypto.GenerateKey()
|
|
||||||
|
|
||||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
|
||||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
|
||||||
)
|
|
||||||
|
|
||||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
|
||||||
statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
|
||||||
statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
|
||||||
statedb.Commit(0, true, false)
|
|
||||||
|
|
||||||
chain := &testBlockChain{
|
|
||||||
config: params.MergedTestChainConfig,
|
|
||||||
basefee: uint256.NewInt(1050),
|
|
||||||
blobfee: uint256.NewInt(105),
|
|
||||||
statedb: statedb,
|
|
||||||
}
|
|
||||||
var timeDiff uint64
|
|
||||||
if accept {
|
|
||||||
timeDiff = uint64(conversionTimeWindow.Seconds()) - 1
|
|
||||||
} else {
|
|
||||||
timeDiff = uint64(conversionTimeWindow.Seconds()) + 1
|
|
||||||
}
|
|
||||||
time := *params.MergedTestChainConfig.OsakaTime + timeDiff
|
|
||||||
chain.setHeadTime(time)
|
|
||||||
|
|
||||||
pool := New(Config{Datadir: t.TempDir()}, chain, nil)
|
|
||||||
if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil {
|
|
||||||
t.Fatalf("failed to create blob pool: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to add legacy blob transactions.
|
|
||||||
var (
|
|
||||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
|
|
||||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion0)
|
|
||||||
txs = []*types.Transaction{tx1, tx2}
|
|
||||||
)
|
|
||||||
errs := pool.Add(txs, true)
|
|
||||||
for _, err := range errs {
|
|
||||||
if accept && err != nil {
|
|
||||||
t.Fatalf("expected tx add to succeed, %v", err)
|
|
||||||
}
|
|
||||||
if !accept && err == nil {
|
|
||||||
t.Fatal("expected tx add to fail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
verifyPoolInternals(t, pool)
|
|
||||||
pool.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBlobs(t *testing.T) {
|
func TestGetBlobs(t *testing.T) {
|
||||||
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
//log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
||||||
|
|
||||||
|
|
@ -1975,7 +1911,6 @@ func TestGetBlobs(t *testing.T) {
|
||||||
limit int
|
limit int
|
||||||
fillRandom bool // Whether to randomly fill some of the requested blobs with unknowns
|
fillRandom bool // Whether to randomly fill some of the requested blobs with unknowns
|
||||||
version byte // Blob sidecar version to request
|
version byte // Blob sidecar version to request
|
||||||
convert bool // Whether to convert version on retrieval
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
start: 0, limit: 6,
|
start: 0, limit: 6,
|
||||||
|
|
@ -2041,11 +1976,6 @@ func TestGetBlobs(t *testing.T) {
|
||||||
start: 0, limit: 18, fillRandom: true,
|
start: 0, limit: 18, fillRandom: true,
|
||||||
version: types.BlobSidecarVersion1,
|
version: types.BlobSidecarVersion1,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
start: 0, limit: 18, fillRandom: true,
|
|
||||||
version: types.BlobSidecarVersion1,
|
|
||||||
convert: true, // Convert some version 0 blobs to version 1 while retrieving
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for i, c := range cases {
|
for i, c := range cases {
|
||||||
var (
|
var (
|
||||||
|
|
@ -2067,7 +1997,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
filled[len(vhashes)] = struct{}{}
|
filled[len(vhashes)] = struct{}{}
|
||||||
vhashes = append(vhashes, testrand.Hash())
|
vhashes = append(vhashes, testrand.Hash())
|
||||||
}
|
}
|
||||||
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version, c.convert)
|
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error for case %d, %v", i, err)
|
t.Errorf("Unexpected error for case %d, %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
@ -2093,8 +2023,7 @@ func TestGetBlobs(t *testing.T) {
|
||||||
// If an item is missing, but shouldn't, error
|
// If an item is missing, but shouldn't, error
|
||||||
if blobs[j] == nil || proofs[j] == nil {
|
if blobs[j] == nil || proofs[j] == nil {
|
||||||
// This is only an error if there was no version mismatch
|
// This is only an error if there was no version mismatch
|
||||||
if c.convert ||
|
if (c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
|
||||||
(c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
|
|
||||||
(c.version == types.BlobSidecarVersion0 && (testBlobIndex < 6 || 12 <= testBlobIndex)) {
|
(c.version == types.BlobSidecarVersion0 && (testBlobIndex < 6 || 12 <= testBlobIndex)) {
|
||||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
|
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
|
||||||
}
|
}
|
||||||
|
|
@ -2121,185 +2050,6 @@ func TestGetBlobs(t *testing.T) {
|
||||||
pool.Close()
|
pool.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSidecarConversion will verify that after the Osaka fork, all legacy
|
|
||||||
// sidecars in the pool are successfully convert to v1 sidecars.
|
|
||||||
func TestSidecarConversion(t *testing.T) {
|
|
||||||
// log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true)))
|
|
||||||
|
|
||||||
// Create a temporary folder for the persistent backend
|
|
||||||
storage := t.TempDir()
|
|
||||||
os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
|
|
||||||
|
|
||||||
var (
|
|
||||||
preOsakaTxs = make(types.Transactions, 10)
|
|
||||||
postOsakaTxs = make(types.Transactions, 3)
|
|
||||||
keys = make([]*ecdsa.PrivateKey, len(preOsakaTxs)+len(postOsakaTxs))
|
|
||||||
addrs = make([]common.Address, len(preOsakaTxs)+len(postOsakaTxs))
|
|
||||||
statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
|
||||||
)
|
|
||||||
for i := range keys {
|
|
||||||
keys[i], _ = crypto.GenerateKey()
|
|
||||||
addrs[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
|
|
||||||
statedb.AddBalance(addrs[i], uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified)
|
|
||||||
}
|
|
||||||
for i := range preOsakaTxs {
|
|
||||||
preOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 2, 0, keys[i], types.BlobSidecarVersion0)
|
|
||||||
}
|
|
||||||
for i := range postOsakaTxs {
|
|
||||||
if i == 0 {
|
|
||||||
// First has a v0 sidecar.
|
|
||||||
postOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 1, 0, keys[len(preOsakaTxs)+i], types.BlobSidecarVersion0)
|
|
||||||
}
|
|
||||||
postOsakaTxs[i] = makeMultiBlobTx(0, 1, 1000, 100, 1, 0, keys[len(preOsakaTxs)+i], types.BlobSidecarVersion1)
|
|
||||||
}
|
|
||||||
statedb.Commit(0, true, false)
|
|
||||||
|
|
||||||
// Test plan:
|
|
||||||
// 1) Create a bunch v0 sidecar txs and add to pool before Osaka.
|
|
||||||
// 2) Pass in new Osaka header to activate the conversion thread.
|
|
||||||
// 3) Continue adding both v0 and v1 transactions to the pool.
|
|
||||||
// 4) Verify that as additional blocks come in, transactions involved in the
|
|
||||||
// migration are correctly discarded.
|
|
||||||
|
|
||||||
config := ¶ms.ChainConfig{
|
|
||||||
ChainID: big.NewInt(1),
|
|
||||||
LondonBlock: big.NewInt(0),
|
|
||||||
BerlinBlock: big.NewInt(0),
|
|
||||||
CancunTime: newUint64(0),
|
|
||||||
PragueTime: newUint64(0),
|
|
||||||
OsakaTime: newUint64(1),
|
|
||||||
BlobScheduleConfig: params.DefaultBlobSchedule,
|
|
||||||
}
|
|
||||||
chain := &testBlockChain{
|
|
||||||
config: config,
|
|
||||||
basefee: uint256.NewInt(1050),
|
|
||||||
blobfee: uint256.NewInt(105),
|
|
||||||
statedb: statedb,
|
|
||||||
blocks: make(map[uint64]*types.Block),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create 3 blocks:
|
|
||||||
// - the current block, before Osaka
|
|
||||||
// - the first block after Osaka
|
|
||||||
// - another post-Osaka block with several transactions in it
|
|
||||||
header0 := chain.CurrentBlock()
|
|
||||||
header0.Time = 0
|
|
||||||
chain.blocks[0] = types.NewBlockWithHeader(header0)
|
|
||||||
|
|
||||||
header1 := chain.CurrentBlock()
|
|
||||||
header1.Number = big.NewInt(1)
|
|
||||||
header1.Time = 1
|
|
||||||
chain.blocks[1] = types.NewBlockWithHeader(header1)
|
|
||||||
|
|
||||||
header2 := chain.CurrentBlock()
|
|
||||||
header2.Time = 2
|
|
||||||
header2.Number = big.NewInt(2)
|
|
||||||
|
|
||||||
// Make a copy of one of the pre-Osaka transactions and convert it to v1 here
|
|
||||||
// so that we can add it to the pool later and ensure a duplicate is not added
|
|
||||||
// by the conversion queue.
|
|
||||||
tx := preOsakaTxs[len(preOsakaTxs)-1]
|
|
||||||
sc := *tx.BlobTxSidecar() // copy sidecar
|
|
||||||
sc.ToV1()
|
|
||||||
tx.WithBlobTxSidecar(&sc)
|
|
||||||
|
|
||||||
block2 := types.NewBlockWithHeader(header2).WithBody(types.Body{Transactions: append(postOsakaTxs, tx)})
|
|
||||||
chain.blocks[2] = block2
|
|
||||||
|
|
||||||
pool := New(Config{Datadir: storage}, chain, nil)
|
|
||||||
if err := pool.Init(1, header0, newReserver()); err != nil {
|
|
||||||
t.Fatalf("failed to create blob pool: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs := pool.Add(preOsakaTxs, true)
|
|
||||||
for i, err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to insert blob tx from %s: %s", addrs[i], errs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kick off migration.
|
|
||||||
pool.Reset(header0, header1)
|
|
||||||
|
|
||||||
// Add the v0 sidecar tx, but don't block so we can keep doing other stuff
|
|
||||||
// while it converts the sidecar.
|
|
||||||
addDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
pool.Add(types.Transactions{postOsakaTxs[0]}, false)
|
|
||||||
close(addDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Add the post-Osaka v1 sidecar txs.
|
|
||||||
errs = pool.Add(postOsakaTxs[1:], false)
|
|
||||||
for _, err := range errs {
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected tx add to succeed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the first tx's conversion to complete, then check that all
|
|
||||||
// transactions added after Osaka can be accounted for in the pool.
|
|
||||||
<-addDone
|
|
||||||
pending := pool.Pending(txpool.PendingFilter{BlobTxs: true, BlobVersion: types.BlobSidecarVersion1})
|
|
||||||
for _, tx := range postOsakaTxs {
|
|
||||||
from, _ := pool.signer.Sender(tx)
|
|
||||||
if len(pending[from]) != 1 || pending[from][0].Hash != tx.Hash() {
|
|
||||||
t.Fatalf("expected post-Osaka txs to be pending")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now update the pool with the next block. This should cause the pool to
|
|
||||||
// clear out the post-Osaka txs since they were included in block 2. Since the
|
|
||||||
// test blockchain doesn't manage nonces, we'll just do that manually before
|
|
||||||
// the reset is called. Don't forget about the pre-Osaka transaction we also
|
|
||||||
// added to block 2!
|
|
||||||
for i := range postOsakaTxs {
|
|
||||||
statedb.SetNonce(addrs[len(preOsakaTxs)+i], 1, tracing.NonceChangeEoACall)
|
|
||||||
}
|
|
||||||
statedb.SetNonce(addrs[len(preOsakaTxs)-1], 1, tracing.NonceChangeEoACall)
|
|
||||||
pool.Reset(header1, block2.Header())
|
|
||||||
|
|
||||||
// Now verify no post-Osaka transactions are tracked by the pool.
|
|
||||||
for i, tx := range postOsakaTxs {
|
|
||||||
if pool.Get(tx.Hash()) != nil {
|
|
||||||
t.Fatalf("expected txs added post-osaka to have been placed in limbo due to inclusion in a block: index %d, hash %s", i, tx.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the pool migration to complete.
|
|
||||||
<-pool.cQueue.anyBillyConversionDone
|
|
||||||
|
|
||||||
// Verify all transactions in the pool were converted and verify the
|
|
||||||
// subsequent cell proofs.
|
|
||||||
count, _ := pool.Stats()
|
|
||||||
if count != len(preOsakaTxs)-1 {
|
|
||||||
t.Errorf("expected pending count to match initial tx count: pending=%d, expected=%d", count, len(preOsakaTxs)-1)
|
|
||||||
}
|
|
||||||
for addr, acc := range pool.index {
|
|
||||||
for _, m := range acc {
|
|
||||||
if m.version != types.BlobSidecarVersion1 {
|
|
||||||
t.Errorf("expected sidecar to have been converted: from %s, hash %s", addr, m.hash)
|
|
||||||
}
|
|
||||||
tx := pool.Get(m.hash)
|
|
||||||
if tx == nil {
|
|
||||||
t.Errorf("failed to get tx by hash: %s", m.hash)
|
|
||||||
}
|
|
||||||
sc := tx.BlobTxSidecar()
|
|
||||||
if err := kzg4844.VerifyCellProofs(sc.Blobs, sc.Commitments, sc.Proofs); err != nil {
|
|
||||||
t.Errorf("failed to verify cell proofs for tx %s after conversion: %s", m.hash, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyPoolInternals(t, pool)
|
|
||||||
|
|
||||||
// Launch conversion a second time.
|
|
||||||
// This is just a sanity check to ensure we can handle it.
|
|
||||||
pool.Reset(header0, header1)
|
|
||||||
|
|
||||||
pool.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fakeBilly is a billy.Database implementation which just drops data on the floor.
|
// fakeBilly is a billy.Database implementation which just drops data on the floor.
|
||||||
type fakeBilly struct {
|
type fakeBilly struct {
|
||||||
billy.Database
|
billy.Database
|
||||||
|
|
@ -2383,5 +2133,3 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUint64(val uint64) *uint64 { return &val }
|
|
||||||
|
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
// Copyright 2025 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 blobpool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"slices"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxPendingConversionTasks caps the number of pending conversion tasks. This
|
|
||||||
// prevents excessive memory usage; the worst-case scenario (2k transactions
|
|
||||||
// with 6 blobs each) would consume approximately 1.5GB of memory.
|
|
||||||
const maxPendingConversionTasks = 2048
|
|
||||||
|
|
||||||
// txConvert represents a conversion task with an attached legacy blob transaction.
|
|
||||||
type txConvert struct {
|
|
||||||
tx *types.Transaction // Legacy blob transaction
|
|
||||||
done chan error // Channel for signaling back if the conversion succeeds
|
|
||||||
}
|
|
||||||
|
|
||||||
// conversionQueue is a dedicated queue for converting legacy blob transactions
|
|
||||||
// received from the network after the Osaka fork. Since conversion is expensive,
|
|
||||||
// it is performed in the background by a single thread, ensuring the main Geth
|
|
||||||
// process is not overloaded.
|
|
||||||
type conversionQueue struct {
|
|
||||||
tasks chan *txConvert
|
|
||||||
startBilly chan func()
|
|
||||||
quit chan struct{}
|
|
||||||
closed chan struct{}
|
|
||||||
|
|
||||||
billyQueue []func()
|
|
||||||
billyTaskDone chan struct{}
|
|
||||||
|
|
||||||
// This channel will be closed when the first billy conversion finishes.
|
|
||||||
// It's added for unit tests to synchronize with the conversion progress.
|
|
||||||
anyBillyConversionDone chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newConversionQueue constructs the conversion queue.
|
|
||||||
func newConversionQueue() *conversionQueue {
|
|
||||||
q := &conversionQueue{
|
|
||||||
tasks: make(chan *txConvert),
|
|
||||||
startBilly: make(chan func()),
|
|
||||||
quit: make(chan struct{}),
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
anyBillyConversionDone: make(chan struct{}),
|
|
||||||
}
|
|
||||||
go q.loop()
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert accepts a legacy blob transaction with version-0 blobs and queues it
|
|
||||||
// for conversion.
|
|
||||||
//
|
|
||||||
// This function may block for a long time until the transaction is processed.
|
|
||||||
func (q *conversionQueue) convert(tx *types.Transaction) error {
|
|
||||||
done := make(chan error, 1)
|
|
||||||
select {
|
|
||||||
case q.tasks <- &txConvert{tx: tx, done: done}:
|
|
||||||
return <-done
|
|
||||||
case <-q.closed:
|
|
||||||
return errors.New("conversion queue closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// launchBillyConversion starts a conversion task in the background.
|
|
||||||
func (q *conversionQueue) launchBillyConversion(fn func()) error {
|
|
||||||
select {
|
|
||||||
case q.startBilly <- fn:
|
|
||||||
return nil
|
|
||||||
case <-q.closed:
|
|
||||||
return errors.New("conversion queue closed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close terminates the conversion queue.
|
|
||||||
func (q *conversionQueue) close() {
|
|
||||||
select {
|
|
||||||
case <-q.closed:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
close(q.quit)
|
|
||||||
<-q.closed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run converts a batch of legacy blob txs to the new cell proof format.
|
|
||||||
func (q *conversionQueue) run(tasks []*txConvert, done chan struct{}, interrupt *atomic.Int32) {
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
for _, t := range tasks {
|
|
||||||
if interrupt != nil && interrupt.Load() != 0 {
|
|
||||||
t.done <- errors.New("conversion is interrupted")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sidecar := t.tx.BlobTxSidecar()
|
|
||||||
if sidecar == nil {
|
|
||||||
t.done <- errors.New("tx without sidecar")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Run the conversion, the original sidecar will be mutated in place
|
|
||||||
start := time.Now()
|
|
||||||
err := sidecar.ToV1()
|
|
||||||
t.done <- err
|
|
||||||
log.Trace("Converted legacy blob tx", "hash", t.tx.Hash(), "err", err, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *conversionQueue) loop() {
|
|
||||||
defer close(q.closed)
|
|
||||||
|
|
||||||
var (
|
|
||||||
done chan struct{} // Non-nil if background routine is active
|
|
||||||
interrupt *atomic.Int32 // Flag to signal conversion interruption
|
|
||||||
|
|
||||||
// The pending tasks for sidecar conversion. We assume the number of legacy
|
|
||||||
// blob transactions requiring conversion will not be excessive. However,
|
|
||||||
// a hard cap is applied as a protective measure.
|
|
||||||
txTasks []*txConvert
|
|
||||||
|
|
||||||
firstBilly = true
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case t := <-q.tasks:
|
|
||||||
if len(txTasks) >= maxPendingConversionTasks {
|
|
||||||
t.done <- errors.New("conversion queue is overloaded")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
txTasks = append(txTasks, t)
|
|
||||||
|
|
||||||
// Launch the background conversion thread if it's idle
|
|
||||||
if done == nil {
|
|
||||||
done, interrupt = make(chan struct{}), new(atomic.Int32)
|
|
||||||
|
|
||||||
tasks := slices.Clone(txTasks)
|
|
||||||
txTasks = txTasks[:0]
|
|
||||||
go q.run(tasks, done, interrupt)
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-done:
|
|
||||||
done, interrupt = nil, nil
|
|
||||||
if len(txTasks) > 0 {
|
|
||||||
done, interrupt = make(chan struct{}), new(atomic.Int32)
|
|
||||||
tasks := slices.Clone(txTasks)
|
|
||||||
txTasks = txTasks[:0]
|
|
||||||
go q.run(tasks, done, interrupt)
|
|
||||||
}
|
|
||||||
|
|
||||||
case fn := <-q.startBilly:
|
|
||||||
q.billyQueue = append(q.billyQueue, fn)
|
|
||||||
q.runNextBillyTask()
|
|
||||||
|
|
||||||
case <-q.billyTaskDone:
|
|
||||||
if firstBilly {
|
|
||||||
close(q.anyBillyConversionDone)
|
|
||||||
firstBilly = false
|
|
||||||
}
|
|
||||||
q.runNextBillyTask()
|
|
||||||
|
|
||||||
case <-q.quit:
|
|
||||||
if done != nil {
|
|
||||||
log.Debug("Waiting for blob proof conversion to exit")
|
|
||||||
interrupt.Store(1)
|
|
||||||
<-done
|
|
||||||
}
|
|
||||||
if q.billyTaskDone != nil {
|
|
||||||
log.Debug("Waiting for blobpool billy conversion to exit")
|
|
||||||
<-q.billyTaskDone
|
|
||||||
}
|
|
||||||
// Signal any tasks that were queued for the next batch but never started
|
|
||||||
// so callers blocked in convert() receive an error instead of hanging.
|
|
||||||
for _, t := range txTasks {
|
|
||||||
// Best-effort notify; t.done is a buffered channel of size 1
|
|
||||||
// created by convert(), and we send exactly once per task.
|
|
||||||
t.done <- errors.New("conversion queue closed")
|
|
||||||
}
|
|
||||||
// Drop references to allow GC of the backing array.
|
|
||||||
txTasks = txTasks[:0]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *conversionQueue) runNextBillyTask() {
|
|
||||||
if len(q.billyQueue) == 0 {
|
|
||||||
q.billyTaskDone = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fn := q.billyQueue[0]
|
|
||||||
q.billyQueue = append(q.billyQueue[:0], q.billyQueue[1:]...)
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() { defer close(done); fn() }()
|
|
||||||
q.billyTaskDone = done
|
|
||||||
}
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
// Copyright 2025 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 blobpool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/holiman/uint256"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createV1BlobTx creates a blob transaction with version 1 sidecar for testing.
|
|
||||||
func createV1BlobTx(nonce uint64, key *ecdsa.PrivateKey) *types.Transaction {
|
|
||||||
blob := &kzg4844.Blob{byte(nonce)}
|
|
||||||
commitment, _ := kzg4844.BlobToCommitment(blob)
|
|
||||||
cellProofs, _ := kzg4844.ComputeCellProofs(blob)
|
|
||||||
|
|
||||||
blobtx := &types.BlobTx{
|
|
||||||
ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID),
|
|
||||||
Nonce: nonce,
|
|
||||||
GasTipCap: uint256.NewInt(1),
|
|
||||||
GasFeeCap: uint256.NewInt(1000),
|
|
||||||
Gas: 21000,
|
|
||||||
BlobFeeCap: uint256.NewInt(100),
|
|
||||||
BlobHashes: []common.Hash{kzg4844.CalcBlobHashV1(sha256.New(), &commitment)},
|
|
||||||
Value: uint256.NewInt(100),
|
|
||||||
Sidecar: types.NewBlobTxSidecar(types.BlobSidecarVersion1, []kzg4844.Blob{*blob}, []kzg4844.Commitment{commitment}, cellProofs),
|
|
||||||
}
|
|
||||||
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConversionQueueBasic(t *testing.T) {
|
|
||||||
queue := newConversionQueue()
|
|
||||||
defer queue.close()
|
|
||||||
|
|
||||||
key, _ := crypto.GenerateKey()
|
|
||||||
tx := makeTx(0, 1, 1, 1, key)
|
|
||||||
if err := queue.convert(tx); err != nil {
|
|
||||||
t.Fatalf("Expected successful conversion, got error: %v", err)
|
|
||||||
}
|
|
||||||
if tx.BlobTxSidecar().Version != types.BlobSidecarVersion1 {
|
|
||||||
t.Errorf("Expected sidecar version to be %d, got %d", types.BlobSidecarVersion1, tx.BlobTxSidecar().Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConversionQueueV1BlobTx(t *testing.T) {
|
|
||||||
queue := newConversionQueue()
|
|
||||||
defer queue.close()
|
|
||||||
|
|
||||||
key, _ := crypto.GenerateKey()
|
|
||||||
tx := createV1BlobTx(0, key)
|
|
||||||
version := tx.BlobTxSidecar().Version
|
|
||||||
|
|
||||||
err := queue.convert(tx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Expected successful conversion, got error: %v", err)
|
|
||||||
}
|
|
||||||
if tx.BlobTxSidecar().Version != version {
|
|
||||||
t.Errorf("Expected sidecar version to remain %d, got %d", version, tx.BlobTxSidecar().Version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConversionQueueClosed(t *testing.T) {
|
|
||||||
queue := newConversionQueue()
|
|
||||||
|
|
||||||
// Close the queue first
|
|
||||||
queue.close()
|
|
||||||
key, _ := crypto.GenerateKey()
|
|
||||||
tx := makeTx(0, 1, 1, 1, key)
|
|
||||||
|
|
||||||
err := queue.convert(tx)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected error when converting on closed queue, got nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConversionQueueDoubleClose(t *testing.T) {
|
|
||||||
queue := newConversionQueue()
|
|
||||||
queue.close()
|
|
||||||
queue.close() // Should not panic
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConversionQueueAutoRestartBatch(t *testing.T) {
|
|
||||||
queue := newConversionQueue()
|
|
||||||
defer queue.close()
|
|
||||||
|
|
||||||
key, _ := crypto.GenerateKey()
|
|
||||||
|
|
||||||
// Create a heavy transaction to ensure the first batch runs long enough
|
|
||||||
// for subsequent tasks to be queued while it is active.
|
|
||||||
heavy := makeMultiBlobTx(0, 1, 1, 1, int(params.BlobTxMaxBlobs), 0, key, types.BlobSidecarVersion0)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(1)
|
|
||||||
heavyDone := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
heavyDone <- queue.convert(heavy)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Give the conversion worker a head start so that the following tasks are
|
|
||||||
// enqueued while the first batch is running.
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
tx1 := makeTx(1, 1, 1, 1, key)
|
|
||||||
tx2 := makeTx(2, 1, 1, 1, key)
|
|
||||||
|
|
||||||
wg.Add(2)
|
|
||||||
done1 := make(chan error, 1)
|
|
||||||
done2 := make(chan error, 1)
|
|
||||||
go func() { defer wg.Done(); done1 <- queue.convert(tx1) }()
|
|
||||||
go func() { defer wg.Done(); done2 <- queue.convert(tx2) }()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-done1:
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("tx1 conversion error: %v", err)
|
|
||||||
}
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
t.Fatal("timeout waiting for tx1 conversion")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-done2:
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("tx2 conversion error: %v", err)
|
|
||||||
}
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
t.Fatal("timeout waiting for tx2 conversion")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-heavyDone:
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("heavy conversion error: %v", err)
|
|
||||||
}
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
t.Fatal("timeout waiting for heavy conversion")
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
if tx1.BlobTxSidecar().Version != types.BlobSidecarVersion1 {
|
|
||||||
t.Fatalf("tx1 sidecar version mismatch: have %d, want %d", tx1.BlobTxSidecar().Version, types.BlobSidecarVersion1)
|
|
||||||
}
|
|
||||||
if tx2.BlobTxSidecar().Version != types.BlobSidecarVersion1 {
|
|
||||||
t.Fatalf("tx2 sidecar version mismatch: have %d, want %d", tx2.BlobTxSidecar().Version, types.BlobSidecarVersion1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -57,7 +56,7 @@ func newLimbo(config *params.ChainConfig, datadir string) (*limbo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new slotter for pre-Osaka blob configuration.
|
// Create new slotter for pre-Osaka blob configuration.
|
||||||
slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(config))
|
slotter := newSlotter(params.BlobTxMaxBlobs)
|
||||||
|
|
||||||
// See if we need to migrate the limbo after fusaka.
|
// See if we need to migrate the limbo after fusaka.
|
||||||
slotter, err := tryMigrate(config, slotter, datadir)
|
slotter, err := tryMigrate(config, slotter, datadir)
|
||||||
|
|
|
||||||
|
|
@ -110,13 +110,3 @@ func (l *lookup) untrack(tx *blobTxMeta) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update updates the transaction index. It should only be used in the conversion.
|
|
||||||
func (l *lookup) update(hash common.Hash, id uint64, size uint64) bool {
|
|
||||||
meta, exists := l.txIndex[hash]
|
|
||||||
if !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
meta.id, meta.size = id, size
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package blobpool
|
package blobpool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/billy"
|
"github.com/holiman/billy"
|
||||||
)
|
)
|
||||||
|
|
@ -42,7 +41,7 @@ func tryMigrate(config *params.ChainConfig, slotter billy.SlotSizeFn, datadir st
|
||||||
// If the version found is less than the currently configured store version,
|
// If the version found is less than the currently configured store version,
|
||||||
// perform a migration then write the updated version of the store.
|
// perform a migration then write the updated version of the store.
|
||||||
if version < storeVersion {
|
if version < storeVersion {
|
||||||
newSlotter := newSlotterEIP7594(eip4844.LatestMaxBlobsPerBlock(config))
|
newSlotter := newSlotterEIP7594(params.BlobTxMaxBlobs)
|
||||||
if err := billy.Migrate(billy.Options{Path: datadir, Repair: true}, slotter, newSlotter); err != nil {
|
if err := billy.Migrate(billy.Options{Path: datadir, Repair: true}, slotter, newSlotter); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +53,7 @@ func tryMigrate(config *params.ChainConfig, slotter billy.SlotSizeFn, datadir st
|
||||||
store.Close()
|
store.Close()
|
||||||
}
|
}
|
||||||
// Set the slotter to the format now that the Osaka is active.
|
// Set the slotter to the format now that the Osaka is active.
|
||||||
slotter = newSlotterEIP7594(eip4844.LatestMaxBlobsPerBlock(config))
|
slotter = newSlotterEIP7594(params.BlobTxMaxBlobs)
|
||||||
}
|
}
|
||||||
return slotter, nil
|
return slotter, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
|
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
|
||||||
}
|
}
|
||||||
// Ensure the transaction can cover floor data gas.
|
// Ensure the transaction can cover floor data gas.
|
||||||
if opts.Config.IsPrague(head.Number, head.Time) {
|
if rules.IsPrague {
|
||||||
floorDataGas, err := core.FloorDataGas(tx.Data())
|
floorDataGas, err := core.FloorDataGas(tx.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -160,6 +160,15 @@ func validateBlobTx(tx *types.Transaction, head *types.Header, opts *ValidationO
|
||||||
if sidecar == nil {
|
if sidecar == nil {
|
||||||
return errors.New("missing sidecar in blob transaction")
|
return errors.New("missing sidecar in blob transaction")
|
||||||
}
|
}
|
||||||
|
// Ensure the sidecar is constructed with the correct version, consistent
|
||||||
|
// with the current fork.
|
||||||
|
version := types.BlobSidecarVersion0
|
||||||
|
if opts.Config.IsOsaka(head.Number, head.Time) {
|
||||||
|
version = types.BlobSidecarVersion1
|
||||||
|
}
|
||||||
|
if sidecar.Version != version {
|
||||||
|
return fmt.Errorf("unexpected sidecar version, want: %d, got: %d", version, sidecar.Version)
|
||||||
|
}
|
||||||
// Ensure the blob fee cap satisfies the minimum blob gas price
|
// Ensure the blob fee cap satisfies the minimum blob gas price
|
||||||
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
if tx.BlobGasFeeCapIntCmp(blobTxMinBlobGasPrice) < 0 {
|
||||||
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
return fmt.Errorf("%w: blob fee cap %v, minimum needed %v", ErrTxGasPriceTooLow, tx.BlobGasFeeCap(), blobTxMinBlobGasPrice)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -299,11 +299,11 @@ func (c *ecrecover) Run(input []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
// We must make sure not to modify the 'input', so placing the 'v' along with
|
// We must make sure not to modify the 'input', so placing the 'v' along with
|
||||||
// the signature needs to be done on a new allocation
|
// the signature needs to be done on a new allocation
|
||||||
sig := make([]byte, 65)
|
var sig [65]byte
|
||||||
copy(sig, input[64:128])
|
copy(sig[:], input[64:128])
|
||||||
sig[64] = v
|
sig[64] = v
|
||||||
// v needs to be at the end for libsecp256k1
|
// v needs to be at the end for libsecp256k1
|
||||||
pubKey, err := crypto.Ecrecover(input[:32], sig)
|
pubKey, err := crypto.Ecrecover(input[:32], sig[:])
|
||||||
// make sure the public key is a valid one
|
// make sure the public key is a valid one
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func Fuzz(data []byte) int {
|
||||||
fmt.Printf("untrusted: %v\n", untrustedComment)
|
fmt.Printf("untrusted: %v\n", untrustedComment)
|
||||||
fmt.Printf("trusted: %v\n", trustedComment)
|
fmt.Printf("trusted: %v\n", trustedComment)
|
||||||
|
|
||||||
err = SignifySignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment)
|
err = SignFile(tmpFile.Name(), tmpFile.Name()+".sig", testSecKey, untrustedComment, trustedComment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ func Fuzz(data []byte) int {
|
||||||
signify = path
|
signify = path
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := exec.LookPath(signify)
|
_, err = exec.LookPath(signify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,11 @@ func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) e
|
||||||
return b.eth.BlockChain().SubscribeChainHeadEvent(ch)
|
return b.eth.BlockChain().SubscribeChainHeadEvent(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeNewPayloadEvent registers a subscription for NewPayloadEvent.
|
||||||
|
func (b *EthAPIBackend) SubscribeNewPayloadEvent(ch chan<- core.NewPayloadEvent) event.Subscription {
|
||||||
|
return b.eth.BlockChain().SubscribeNewPayloadEvent(ch)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||||
return b.eth.BlockChain().SubscribeLogsEvent(ch)
|
return b.eth.BlockChain().SubscribeLogsEvent(ch)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
"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/core"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||||
"github.com/ethereum/go-ethereum/internal/version"
|
"github.com/ethereum/go-ethereum/internal/version"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -81,20 +81,6 @@ const (
|
||||||
beaconUpdateWarnFrequency = 5 * time.Minute
|
beaconUpdateWarnFrequency = 5 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// Number of blobs requested via getBlobsV2
|
|
||||||
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
|
|
||||||
|
|
||||||
// Number of blobs requested via getBlobsV2 that are present in the blobpool
|
|
||||||
getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)
|
|
||||||
|
|
||||||
// Number of times getBlobsV2 responded with “hit”
|
|
||||||
getBlobsV2RequestHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil)
|
|
||||||
|
|
||||||
// Number of times getBlobsV2 responded with “miss”
|
|
||||||
getBlobsV2RequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConsensusAPI struct {
|
type ConsensusAPI struct {
|
||||||
eth *eth.Ethereum
|
eth *eth.Ethereum
|
||||||
|
|
||||||
|
|
@ -137,6 +123,9 @@ type ConsensusAPI struct {
|
||||||
|
|
||||||
// NewConsensusAPI creates a new consensus api for the given backend.
|
// NewConsensusAPI creates a new consensus api for the given backend.
|
||||||
// The underlying blockchain needs to have a valid terminal total difficulty set.
|
// The underlying blockchain needs to have a valid terminal total difficulty set.
|
||||||
|
//
|
||||||
|
// This function creates a long-lived object with an attached background thread.
|
||||||
|
// For testing or other short-term use cases, please use newConsensusAPIWithoutHeartbeat.
|
||||||
func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
|
func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
|
||||||
api := newConsensusAPIWithoutHeartbeat(eth)
|
api := newConsensusAPIWithoutHeartbeat(eth)
|
||||||
go api.heartbeat()
|
go api.heartbeat()
|
||||||
|
|
@ -517,7 +506,7 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
|
||||||
if len(hashes) > 128 {
|
if len(hashes) > 128 {
|
||||||
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
|
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
|
||||||
}
|
}
|
||||||
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0, false)
|
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, engine.InvalidParams.With(err)
|
return nil, engine.InvalidParams.With(err)
|
||||||
}
|
}
|
||||||
|
|
@ -563,8 +552,25 @@ func (api *ConsensusAPI) GetBlobsV1(hashes []common.Hash) ([]*engine.BlobAndProo
|
||||||
func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
|
func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
|
||||||
head := api.eth.BlockChain().CurrentHeader()
|
head := api.eth.BlockChain().CurrentHeader()
|
||||||
if api.config().LatestFork(head.Time) < forks.Osaka {
|
if api.config().LatestFork(head.Time) < forks.Osaka {
|
||||||
return nil, unsupportedForkErr("engine_getBlobsV2 is not available before Osaka fork")
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
return api.getBlobs(hashes, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlobsV3 returns a set of blobs from the transaction pool. Same as
|
||||||
|
// GetBlobsV2, except will return partial responses in case there is a missing
|
||||||
|
// blob.
|
||||||
|
func (api *ConsensusAPI) GetBlobsV3(hashes []common.Hash) ([]*engine.BlobAndProofV2, error) {
|
||||||
|
head := api.eth.BlockChain().CurrentHeader()
|
||||||
|
if api.config().LatestFork(head.Time) < forks.Osaka {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return api.getBlobs(hashes, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBlobs returns all available blobs. In v2, partial responses are not allowed,
|
||||||
|
// while v3 supports partial responses.
|
||||||
|
func (api *ConsensusAPI) getBlobs(hashes []common.Hash, v2 bool) ([]*engine.BlobAndProofV2, error) {
|
||||||
if len(hashes) > 128 {
|
if len(hashes) > 128 {
|
||||||
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
|
return nil, engine.TooLargeRequest.With(fmt.Errorf("requested blob count too large: %v", len(hashes)))
|
||||||
}
|
}
|
||||||
|
|
@ -572,28 +578,30 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
|
||||||
getBlobsRequestedCounter.Inc(int64(len(hashes)))
|
getBlobsRequestedCounter.Inc(int64(len(hashes)))
|
||||||
getBlobsAvailableCounter.Inc(int64(available))
|
getBlobsAvailableCounter.Inc(int64(available))
|
||||||
|
|
||||||
// Optimization: check first if all blobs are available, if not, return empty response
|
// Short circuit if partial response is not allowed
|
||||||
if available != len(hashes) {
|
if v2 && available != len(hashes) {
|
||||||
getBlobsV2RequestMiss.Inc(1)
|
getBlobsRequestMiss.Inc(1)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
// Retrieve blobs from the pool. This operation is expensive and may involve
|
||||||
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1, false)
|
// heavy disk I/O.
|
||||||
|
blobs, _, proofs, err := api.eth.BlobTxPool().GetBlobs(hashes, types.BlobSidecarVersion1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, engine.InvalidParams.With(err)
|
return nil, engine.InvalidParams.With(err)
|
||||||
}
|
}
|
||||||
|
// Validate the blobs from the pool and assemble the response
|
||||||
// To comply with API spec, check again that we really got all data needed
|
|
||||||
for _, blob := range blobs {
|
|
||||||
if blob == nil {
|
|
||||||
getBlobsV2RequestMiss.Inc(1)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getBlobsV2RequestHit.Inc(1)
|
|
||||||
|
|
||||||
res := make([]*engine.BlobAndProofV2, len(hashes))
|
res := make([]*engine.BlobAndProofV2, len(hashes))
|
||||||
for i := 0; i < len(blobs); i++ {
|
for i := range blobs {
|
||||||
|
// The blob has been evicted since the last AvailableBlobs call.
|
||||||
|
// Return null if partial response is not allowed.
|
||||||
|
if blobs[i] == nil {
|
||||||
|
if !v2 {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
getBlobsRequestMiss.Inc(1)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
var cellProofs []hexutil.Bytes
|
var cellProofs []hexutil.Bytes
|
||||||
for _, proof := range proofs[i] {
|
for _, proof := range proofs[i] {
|
||||||
cellProofs = append(cellProofs, proof[:])
|
cellProofs = append(cellProofs, proof[:])
|
||||||
|
|
@ -603,6 +611,13 @@ func (api *ConsensusAPI) GetBlobsV2(hashes []common.Hash) ([]*engine.BlobAndProo
|
||||||
CellProofs: cellProofs,
|
CellProofs: cellProofs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(res) == len(hashes) {
|
||||||
|
getBlobsRequestCompleteHit.Inc(1)
|
||||||
|
} else if len(res) > 0 {
|
||||||
|
getBlobsRequestPartialHit.Inc(1)
|
||||||
|
} else {
|
||||||
|
getBlobsRequestMiss.Inc(1)
|
||||||
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -757,7 +772,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
||||||
return api.delayPayloadImport(block), nil
|
return api.delayPayloadImport(block), nil
|
||||||
}
|
}
|
||||||
if block.Time() <= parent.Time() {
|
if block.Time() <= parent.Time() {
|
||||||
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
|
log.Warn("Invalid timestamp", "parent", parent.Time(), "block", block.Time())
|
||||||
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
|
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
|
||||||
}
|
}
|
||||||
// Another corner case: if the node is in snap sync mode, but the CL client
|
// Another corner case: if the node is in snap sync mode, but the CL client
|
||||||
|
|
@ -773,7 +788,9 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
||||||
return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
|
return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
|
||||||
}
|
}
|
||||||
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
|
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
|
||||||
|
start := time.Now()
|
||||||
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)
|
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)
|
||||||
|
processingTime := time.Since(start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("NewPayload: inserting block failed", "error", err)
|
log.Warn("NewPayload: inserting block failed", "error", err)
|
||||||
|
|
||||||
|
|
@ -786,6 +803,13 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
||||||
}
|
}
|
||||||
hash := block.Hash()
|
hash := block.Hash()
|
||||||
|
|
||||||
|
// Emit NewPayloadEvent for ethstats reporting
|
||||||
|
api.eth.BlockChain().SendNewPayloadEvent(core.NewPayloadEvent{
|
||||||
|
Hash: hash,
|
||||||
|
Number: block.NumberU64(),
|
||||||
|
ProcessingTime: processingTime,
|
||||||
|
})
|
||||||
|
|
||||||
// If witness collection was requested, inject that into the result too
|
// If witness collection was requested, inject that into the result too
|
||||||
var ow *hexutil.Bytes
|
var ow *hexutil.Bytes
|
||||||
if proofs != nil {
|
if proofs != nil {
|
||||||
|
|
@ -818,7 +842,7 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) engine.PayloadSt
|
||||||
return engine.PayloadStatusV1{Status: engine.SYNCING}
|
return engine.PayloadStatusV1{Status: engine.SYNCING}
|
||||||
}
|
}
|
||||||
// Either no beacon sync was started yet, or it rejected the delivered
|
// Either no beacon sync was started yet, or it rejected the delivered
|
||||||
// payload as non-integratable on top of the existing sync. We'll just
|
// payload as non-integrate on top of the existing sync. We'll just
|
||||||
// have to rely on the beacon client to forcefully update the head with
|
// have to rely on the beacon client to forcefully update the head with
|
||||||
// a forkchoice update request.
|
// a forkchoice update request.
|
||||||
if api.eth.Downloader().ConfigSyncMode() == ethconfig.FullSync {
|
if api.eth.Downloader().ConfigSyncMode() == ethconfig.FullSync {
|
||||||
|
|
@ -916,8 +940,6 @@ func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.Pa
|
||||||
// heartbeat loops indefinitely, and checks if there have been beacon client updates
|
// heartbeat loops indefinitely, and checks if there have been beacon client updates
|
||||||
// received in the last while. If not - or if they but strange ones - it warns the
|
// received in the last while. If not - or if they but strange ones - it warns the
|
||||||
// user that something might be off with their consensus node.
|
// user that something might be off with their consensus node.
|
||||||
//
|
|
||||||
// TODO(karalabe): Spin this goroutine down somehow
|
|
||||||
func (api *ConsensusAPI) heartbeat() {
|
func (api *ConsensusAPI) heartbeat() {
|
||||||
// Sleep a bit on startup since there's obviously no beacon client yet
|
// Sleep a bit on startup since there's obviously no beacon client yet
|
||||||
// attached, so no need to print scary warnings to the user.
|
// attached, so no need to print scary warnings to the user.
|
||||||
|
|
|
||||||
|
|
@ -2016,7 +2016,7 @@ func TestGetBlobsV1AfterOsakaFork(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetBlobsV2(t *testing.T) {
|
func TestGetBlobsV2And3(t *testing.T) {
|
||||||
n, api := newGetBlobEnv(t, 1)
|
n, api := newGetBlobEnv(t, 1)
|
||||||
defer n.Close()
|
defer n.Close()
|
||||||
|
|
||||||
|
|
@ -2045,7 +2045,8 @@ func TestGetBlobsV2(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, suite := range suites {
|
for i, suite := range suites {
|
||||||
runGetBlobsV2(t, api, suite.start, suite.limit, suite.fillRandom, fmt.Sprintf("suite=%d", i))
|
runGetBlobs(t, api.GetBlobsV2, suite.start, suite.limit, suite.fillRandom, false, fmt.Sprintf("GetBlobsV2 suite=%d", i))
|
||||||
|
runGetBlobs(t, api.GetBlobsV3, suite.start, suite.limit, suite.fillRandom, true, fmt.Sprintf("GetBlobsV3 suite=%d %v", i, suite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2060,22 +2061,20 @@ func BenchmarkGetBlobsV2(b *testing.B) {
|
||||||
name := fmt.Sprintf("blobs=%d", blobs)
|
name := fmt.Sprintf("blobs=%d", blobs)
|
||||||
b.Run(name, func(b *testing.B) {
|
b.Run(name, func(b *testing.B) {
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
runGetBlobsV2(b, api, 0, blobs, false, name)
|
runGetBlobs(b, api.GetBlobsV2, 0, blobs, false, false, name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGetBlobsV2(t testing.TB, api *ConsensusAPI, start, limit int, fillRandom bool, name string) {
|
type getBlobsFn func(hashes []common.Hash) ([]*engine.BlobAndProofV2, error)
|
||||||
|
|
||||||
|
func runGetBlobs(t testing.TB, getBlobs getBlobsFn, start, limit int, fillRandom bool, expectPartialResponse bool, name string) {
|
||||||
// Fill the request for retrieving blobs
|
// Fill the request for retrieving blobs
|
||||||
var (
|
var (
|
||||||
vhashes []common.Hash
|
vhashes []common.Hash
|
||||||
expect []*engine.BlobAndProofV2
|
expect []*engine.BlobAndProofV2
|
||||||
)
|
)
|
||||||
// fill missing blob
|
|
||||||
if fillRandom {
|
|
||||||
vhashes = append(vhashes, testrand.Hash())
|
|
||||||
}
|
|
||||||
for j := start; j < limit; j++ {
|
for j := start; j < limit; j++ {
|
||||||
vhashes = append(vhashes, testBlobVHashes[j])
|
vhashes = append(vhashes, testBlobVHashes[j])
|
||||||
var cellProofs []hexutil.Bytes
|
var cellProofs []hexutil.Bytes
|
||||||
|
|
@ -2087,13 +2086,21 @@ func runGetBlobsV2(t testing.TB, api *ConsensusAPI, start, limit int, fillRandom
|
||||||
CellProofs: cellProofs,
|
CellProofs: cellProofs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
result, err := api.GetBlobsV2(vhashes)
|
// fill missing blob
|
||||||
|
if fillRandom {
|
||||||
|
vhashes = append(vhashes, testrand.Hash())
|
||||||
|
}
|
||||||
|
result, err := getBlobs(vhashes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error for case %s, %v", name, err)
|
t.Errorf("Unexpected error for case %s, %v", name, err)
|
||||||
}
|
}
|
||||||
// null is responded if any blob is missing
|
|
||||||
if fillRandom {
|
if fillRandom {
|
||||||
expect = nil
|
if expectPartialResponse {
|
||||||
|
expect = append(expect, nil)
|
||||||
|
} else {
|
||||||
|
// Nil is expected if getBlobs can not return a partial response
|
||||||
|
expect = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(result, expect) {
|
if !reflect.DeepEqual(result, expect) {
|
||||||
t.Fatalf("Unexpected result for case %s", name)
|
t.Fatalf("Unexpected result for case %s", name)
|
||||||
|
|
|
||||||
37
eth/catalyst/metrics.go
Normal file
37
eth/catalyst/metrics.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2025 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 catalyst
|
||||||
|
|
||||||
|
import "github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Number of blobs requested via getBlobsV2
|
||||||
|
getBlobsRequestedCounter = metrics.NewRegisteredCounter("engine/getblobs/requested", nil)
|
||||||
|
|
||||||
|
// Number of blobs requested via getBlobsV2 that are present in the blobpool
|
||||||
|
getBlobsAvailableCounter = metrics.NewRegisteredCounter("engine/getblobs/available", nil)
|
||||||
|
|
||||||
|
// Number of times getBlobsV2/V3 responded with all blobs
|
||||||
|
getBlobsRequestCompleteHit = metrics.NewRegisteredCounter("engine/getblobs/hit", nil)
|
||||||
|
|
||||||
|
// Number of times getBlobsV2/V3 responded with no blobs. V2 will return no
|
||||||
|
// blobs if it doesn't have all the blobs (all or nothing).
|
||||||
|
getBlobsRequestMiss = metrics.NewRegisteredCounter("engine/getblobs/miss", nil)
|
||||||
|
|
||||||
|
// Number of times getBlobsV3 responded with some, but not all, blobs
|
||||||
|
getBlobsRequestPartialHit = metrics.NewRegisteredCounter("engine/getblobs/partial", nil)
|
||||||
|
)
|
||||||
|
|
@ -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,12 +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 {
|
||||||
return filled // Return the filled header on the previous sync completion
|
// Sync cycle was inactive, retrieve and return the latest snap block
|
||||||
|
// as the filled header.
|
||||||
|
log.Debug("Backfiller was inactive")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -73,9 +75,9 @@ func (b *beaconBackfiller) suspend() *types.Header {
|
||||||
// Now that we're sure the downloader successfully started up, we can cancel
|
// Now that we're sure the downloader successfully started up, we can cancel
|
||||||
// it safely without running the risk of data races.
|
// it safely without running the risk of data races.
|
||||||
b.downloader.Cancel()
|
b.downloader.Cancel()
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,10 +88,10 @@ func (b *beaconBackfiller) resume() {
|
||||||
// If a previous filling cycle is still running, just ignore this start
|
// If a previous filling cycle is still running, just ignore this start
|
||||||
// request. // TODO(karalabe): We should make this channel driven
|
// request. // TODO(karalabe): We should make this channel driven
|
||||||
b.lock.Unlock()
|
b.lock.Unlock()
|
||||||
|
log.Debug("Backfiller is running")
|
||||||
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()
|
||||||
|
|
||||||
|
|
@ -100,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
|
||||||
|
|
@ -110,11 +111,13 @@ 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()
|
||||||
}
|
}
|
||||||
|
log.Debug("Backfilling completed")
|
||||||
}()
|
}()
|
||||||
|
log.Debug("Backfilling started")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBadBlockCallback sets the callback to run when a bad block is hit by the
|
// SetBadBlockCallback sets the callback to run when a bad block is hit by the
|
||||||
|
|
@ -183,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:
|
||||||
|
|
@ -236,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,8 +193,12 @@ type BlockChain interface {
|
||||||
// CurrentSnapBlock retrieves the head snap block from the local chain.
|
// CurrentSnapBlock retrieves the head snap block from the local chain.
|
||||||
CurrentSnapBlock() *types.Header
|
CurrentSnapBlock() *types.Header
|
||||||
|
|
||||||
// SnapSyncCommitHead directly commits the head block to a certain entity.
|
// SnapSyncStart explicitly notifies the chain that snap sync is scheduled and
|
||||||
SnapSyncCommitHead(common.Hash) error
|
// marks chain mutations as disallowed.
|
||||||
|
SnapSyncStart() error
|
||||||
|
|
||||||
|
// SnapSyncComplete directly commits the head block to a certain entity.
|
||||||
|
SnapSyncComplete(common.Hash) error
|
||||||
|
|
||||||
// InsertHeadersBeforeCutoff inserts a batch of headers before the configured
|
// InsertHeadersBeforeCutoff inserts a batch of headers before the configured
|
||||||
// chain cutoff into the ancient store.
|
// chain cutoff into the ancient store.
|
||||||
|
|
@ -244,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
|
||||||
|
|
@ -361,28 +365,21 @@ func (d *Downloader) synchronise(beaconPing chan struct{}) (err error) {
|
||||||
if d.notified.CompareAndSwap(false, true) {
|
if d.notified.CompareAndSwap(false, true) {
|
||||||
log.Info("Block synchronisation started")
|
log.Info("Block synchronisation started")
|
||||||
}
|
}
|
||||||
mode := d.moder.get()
|
|
||||||
|
// Obtain the synchronized used in this cycle
|
||||||
|
mode := d.moder.get(true)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err == nil && mode == ethconfig.SnapSync {
|
if err == nil && mode == ethconfig.SnapSync {
|
||||||
d.moder.disableSnap()
|
d.moder.disableSnap()
|
||||||
log.Info("Disabled snap-sync after the initial sync cycle")
|
log.Info("Disabled snap-sync after the initial sync cycle")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Disable chain mutations when snap sync is selected, ensuring the
|
||||||
|
// downloader is the sole mutator.
|
||||||
if mode == ethconfig.SnapSync {
|
if mode == ethconfig.SnapSync {
|
||||||
// Snap sync will directly modify the persistent state, making the entire
|
if err := d.blockchain.SnapSyncStart(); err != nil {
|
||||||
// trie database unusable until the state is fully synced. To prevent any
|
return err
|
||||||
// subsequent state reads, explicitly disable the trie database and state
|
|
||||||
// syncer is responsible to address and correct any state missing.
|
|
||||||
if d.blockchain.TrieDB().Scheme() == rawdb.PathScheme {
|
|
||||||
if err := d.blockchain.TrieDB().Disable(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Snap sync uses the snapshot namespace to store potentially flaky data until
|
|
||||||
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
|
|
||||||
// time to prevent access.
|
|
||||||
if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests
|
|
||||||
snapshots.Disable()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reset the queue, peer set and wake channels to clean any internal leftover state
|
// Reset the queue, peer set and wake channels to clean any internal leftover state
|
||||||
|
|
@ -427,7 +424,7 @@ func (d *Downloader) getMode() SyncMode {
|
||||||
// ConfigSyncMode returns the sync mode configured for the node.
|
// ConfigSyncMode returns the sync mode configured for the node.
|
||||||
// The actual running sync mode can differ from this.
|
// The actual running sync mode can differ from this.
|
||||||
func (d *Downloader) ConfigSyncMode() SyncMode {
|
func (d *Downloader) ConfigSyncMode() SyncMode {
|
||||||
return d.moder.get()
|
return d.moder.get(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// syncToHead starts a block synchronization based on the hash chain from
|
// syncToHead starts a block synchronization based on the hash chain from
|
||||||
|
|
@ -1086,7 +1083,7 @@ func (d *Downloader) commitPivotBlock(result *fetchResult) error {
|
||||||
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []rlp.RawValue{result.Receipts}, d.ancientLimit); err != nil {
|
if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []rlp.RawValue{result.Receipts}, d.ancientLimit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := d.blockchain.SnapSyncCommitHead(block.Hash()); err != nil {
|
if err := d.blockchain.SnapSyncComplete(block.Hash()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.committed.Store(true)
|
d.committed.Store(true)
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ var errSyncMerged = errors.New("sync merged")
|
||||||
// should abort and restart with the new state.
|
// should abort and restart with the new state.
|
||||||
var errSyncReorged = errors.New("sync reorged")
|
var errSyncReorged = errors.New("sync reorged")
|
||||||
|
|
||||||
|
// errSyncTrimmed is an internal helper error to signal that the local chain
|
||||||
|
// has been trimmed (e.g, via debug_setHead explicitly) and the skeleton chain
|
||||||
|
// is no longer linked with the local chain. In this case, the skeleton sync
|
||||||
|
// should be re-scheduled again.
|
||||||
|
var errSyncTrimmed = errors.New("sync trimmed")
|
||||||
|
|
||||||
// errTerminated is returned if the sync mechanism was terminated for this run of
|
// errTerminated is returned if the sync mechanism was terminated for this run of
|
||||||
// the process. This is usually the case when Geth is shutting down and some events
|
// the process. This is usually the case when Geth is shutting down and some events
|
||||||
// might still be propagating.
|
// might still be propagating.
|
||||||
|
|
@ -201,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
|
||||||
|
|
@ -225,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),
|
||||||
|
|
@ -296,6 +310,11 @@ func (s *skeleton) startup() {
|
||||||
// head to force a cleanup.
|
// head to force a cleanup.
|
||||||
head = newhead
|
head = newhead
|
||||||
|
|
||||||
|
case err == errSyncTrimmed:
|
||||||
|
// The skeleton chain is not linked with the local chain anymore,
|
||||||
|
// restart the sync.
|
||||||
|
head = nil
|
||||||
|
|
||||||
case err == errTerminated:
|
case err == errTerminated:
|
||||||
// Sync was requested to be terminated from within, stop and
|
// Sync was requested to be terminated from within, stop and
|
||||||
// return (no need to pass a message, was already done internally)
|
// return (no need to pass a message, was already done internally)
|
||||||
|
|
@ -343,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.
|
||||||
|
|
@ -367,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()
|
||||||
}
|
}
|
||||||
|
|
@ -486,7 +525,17 @@ 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 {
|
||||||
s.filler.resume()
|
if len(s.progress.Subchains) == 1 && s.linked(s.scratchHead, s.progress.Subchains[0].Next) {
|
||||||
|
// 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.
|
||||||
|
s.filler.resume()
|
||||||
|
} else {
|
||||||
|
// The skeleton chain is no longer linked to the local chain for some reason
|
||||||
|
// (e.g. debug_setHead was used to trim the local chain). Re-schedule the
|
||||||
|
// skeleton sync to fill the chain gap.
|
||||||
|
log.Warn("Local chain has been trimmed", "tailnumber", s.scratchHead, "tailhash", s.progress.Subchains[0].Next)
|
||||||
|
return nil, errSyncTrimmed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case req := <-requestFails:
|
case req := <-requestFails:
|
||||||
|
|
@ -649,9 +698,19 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error
|
||||||
// Not a noop / double head announce, abort with a reorg
|
// Not a noop / double head announce, abort with a reorg
|
||||||
return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number)
|
return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number)
|
||||||
}
|
}
|
||||||
|
// Terminate the sync if the chain head is gapped
|
||||||
if lastchain.Head+1 < number {
|
if lastchain.Head+1 < number {
|
||||||
return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number)
|
return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number)
|
||||||
}
|
}
|
||||||
|
// Ignore the duplicated beacon header announcement
|
||||||
|
if lastchain.Head == number {
|
||||||
|
local := rawdb.ReadSkeletonHeader(s.db, number)
|
||||||
|
if local != nil && local.Hash() == head.Hash() {
|
||||||
|
log.Debug("Ignored the identical beacon header", "number", number, "hash", local.Hash())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Terminate the sync if the chain head is forked
|
||||||
if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash {
|
if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash {
|
||||||
return fmt.Errorf("%w, ancestor: %d, hash: %s, want: %s", errChainForked, number-1, parent.Hash(), head.ParentHash)
|
return fmt.Errorf("%w, ancestor: %d, hash: %s, want: %s", errChainForked, number-1, parent.Hash(), head.ParentHash)
|
||||||
}
|
}
|
||||||
|
|
@ -669,6 +728,7 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write skeleton sync status", "err", err)
|
log.Crit("Failed to write skeleton sync status", "err", err)
|
||||||
}
|
}
|
||||||
|
log.Debug("Extended beacon header chain", "number", head.Number, "hash", head.Hash())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -909,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))
|
||||||
|
|
||||||
|
|
@ -982,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -999,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
|
||||||
|
|
@ -1056,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
|
||||||
}
|
}
|
||||||
|
|
@ -1121,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
|
||||||
|
|
@ -1206,6 +1278,7 @@ func (s *skeleton) cleanStales(filled *types.Header) error {
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write beacon trim data", "err", err)
|
log.Crit("Failed to write beacon trim data", "err", err)
|
||||||
}
|
}
|
||||||
|
log.Debug("Cleaned stale beacon headers", "start", start, "end", end)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func newSyncModer(mode ethconfig.SyncMode, chain BlockChain, disk ethdb.KeyValue
|
||||||
|
|
||||||
// get retrieves the current sync mode, either explicitly set, or derived
|
// get retrieves the current sync mode, either explicitly set, or derived
|
||||||
// from the chain status.
|
// from the chain status.
|
||||||
func (m *syncModer) get() ethconfig.SyncMode {
|
func (m *syncModer) get(report bool) ethconfig.SyncMode {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -83,12 +83,16 @@ func (m *syncModer) get() ethconfig.SyncMode {
|
||||||
if m.mode == ethconfig.SnapSync {
|
if m.mode == ethconfig.SnapSync {
|
||||||
return ethconfig.SnapSync
|
return ethconfig.SnapSync
|
||||||
}
|
}
|
||||||
|
logger := log.Debug
|
||||||
|
if report {
|
||||||
|
logger = log.Info
|
||||||
|
}
|
||||||
// We are probably in full sync, but we might have rewound to before the
|
// We are probably in full sync, but we might have rewound to before the
|
||||||
// snap sync pivot, check if we should re-enable snap sync.
|
// snap sync pivot, check if we should re-enable snap sync.
|
||||||
head := m.chain.CurrentBlock()
|
head := m.chain.CurrentBlock()
|
||||||
if pivot := rawdb.ReadLastPivotNumber(m.disk); pivot != nil {
|
if pivot := rawdb.ReadLastPivotNumber(m.disk); pivot != nil {
|
||||||
if head.Number.Uint64() < *pivot {
|
if head.Number.Uint64() < *pivot {
|
||||||
log.Info("Reenabled snap-sync as chain is lagging behind the pivot", "head", head.Number, "pivot", pivot)
|
logger("Reenabled snap-sync as chain is lagging behind the pivot", "head", head.Number, "pivot", pivot)
|
||||||
return ethconfig.SnapSync
|
return ethconfig.SnapSync
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +100,7 @@ func (m *syncModer) get() ethconfig.SyncMode {
|
||||||
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
|
// the head state, forcefully rerun the snap sync. Note it doesn't mean the
|
||||||
// persistent state is corrupted, just mismatch with the head block.
|
// persistent state is corrupted, just mismatch with the head block.
|
||||||
if !m.chain.HasState(head.Root) {
|
if !m.chain.HasState(head.Root) {
|
||||||
log.Info("Reenabled snap-sync as chain is stateless")
|
logger("Reenabled snap-sync as chain is stateless")
|
||||||
return ethconfig.SnapSync
|
return ethconfig.SnapSync
|
||||||
}
|
}
|
||||||
// Nope, we're really full syncing
|
// Nope, we're really full syncing
|
||||||
|
|
|
||||||
|
|
@ -352,9 +352,9 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool)
|
||||||
otherRejectMeter.Mark(otherreject)
|
otherRejectMeter.Mark(otherreject)
|
||||||
|
|
||||||
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
|
// If 'other reject' is >25% of the deliveries in any batch, sleep a bit.
|
||||||
if otherreject > addTxsBatchSize/4 {
|
if otherreject > int64((len(batch)+3)/4) {
|
||||||
|
log.Debug("Peer delivering stale or invalid transactions", "peer", peer, "rejected", otherreject)
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
log.Debug("Peer delivering stale transactions", "peer", peer, "rejected", otherreject)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -509,7 +509,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
||||||
annCount += len(hashes)
|
annCount += len(hashes)
|
||||||
peer.AsyncSendPooledTransactionHashes(hashes)
|
peer.AsyncSendPooledTransactionHashes(hashes)
|
||||||
}
|
}
|
||||||
log.Debug("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs,
|
log.Trace("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs,
|
||||||
"bcastpeers", len(txset), "bcastcount", directCount, "annpeers", len(annos), "anncount", annCount)
|
"bcastpeers", len(txset), "bcastcount", directCount, "annpeers", len(annos), "anncount", annCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,7 @@ func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Requ
|
||||||
|
|
||||||
// RequestTxs fetches a batch of transactions from a remote node.
|
// RequestTxs fetches a batch of transactions from a remote node.
|
||||||
func (p *Peer) RequestTxs(hashes []common.Hash) error {
|
func (p *Peer) RequestTxs(hashes []common.Hash) error {
|
||||||
p.Log().Debug("Fetching batch of transactions", "count", len(hashes))
|
p.Log().Trace("Fetching batch of transactions", "count", len(hashes))
|
||||||
id := rand.Uint64()
|
id := rand.Uint64()
|
||||||
|
|
||||||
requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id)
|
requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id)
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ type prestateTracer struct {
|
||||||
pre stateMap
|
pre stateMap
|
||||||
post stateMap
|
post stateMap
|
||||||
to common.Address
|
to common.Address
|
||||||
config prestateTracerConfig
|
config PrestateTracerConfig
|
||||||
chainConfig *params.ChainConfig
|
chainConfig *params.ChainConfig
|
||||||
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
interrupt atomic.Bool // Atomic flag to signal execution interruption
|
||||||
reason error // Textual reason for the interruption
|
reason error // Textual reason for the interruption
|
||||||
|
|
@ -74,7 +74,7 @@ type prestateTracer struct {
|
||||||
deleted map[common.Address]bool
|
deleted map[common.Address]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type prestateTracerConfig struct {
|
type PrestateTracerConfig struct {
|
||||||
DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
|
DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications
|
||||||
DisableCode bool `json:"disableCode"` // If true, this tracer will not return the contract code
|
DisableCode bool `json:"disableCode"` // If true, this tracer will not return the contract code
|
||||||
DisableStorage bool `json:"disableStorage"` // If true, this tracer will not return the contract storage
|
DisableStorage bool `json:"disableStorage"` // If true, this tracer will not return the contract storage
|
||||||
|
|
@ -82,7 +82,7 @@ type prestateTracerConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) {
|
func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) {
|
||||||
var config prestateTracerConfig
|
var config PrestateTracerConfig
|
||||||
if err := json.Unmarshal(cfg, &config); err != nil {
|
if err := json.Unmarshal(cfg, &config); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,10 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s
|
||||||
|
|
||||||
var res accountResult
|
var res accountResult
|
||||||
err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, toBlockNumArg(blockNumber))
|
err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, toBlockNumArg(blockNumber))
|
||||||
// Turn hexutils back to normal datatypes
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Turn hexutils back to normal data types
|
||||||
storageResults := make([]StorageResult, 0, len(res.StorageProof))
|
storageResults := make([]StorageResult, 0, len(res.StorageProof))
|
||||||
for _, st := range res.StorageProof {
|
for _, st := range res.StorageProof {
|
||||||
storageResults = append(storageResults, StorageResult{
|
storageResults = append(storageResults, StorageResult{
|
||||||
|
|
@ -122,7 +125,7 @@ func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []s
|
||||||
StorageHash: res.StorageHash,
|
StorageHash: res.StorageHash,
|
||||||
StorageProof: storageResults,
|
StorageProof: storageResults,
|
||||||
}
|
}
|
||||||
return &result, err
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallContract executes a message call transaction, which is directly executed in the VM
|
// CallContract executes a message call transaction, which is directly executed in the VM
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ const (
|
||||||
type backend interface {
|
type backend interface {
|
||||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||||
SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
|
SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription
|
||||||
|
SubscribeNewPayloadEvent(ch chan<- core.NewPayloadEvent) event.Subscription
|
||||||
CurrentHeader() *types.Header
|
CurrentHeader() *types.Header
|
||||||
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||||
Stats() (pending int, queued int)
|
Stats() (pending int, queued int)
|
||||||
|
|
@ -92,8 +93,9 @@ type Service struct {
|
||||||
pongCh chan struct{} // Pong notifications are fed into this channel
|
pongCh chan struct{} // Pong notifications are fed into this channel
|
||||||
histCh chan []uint64 // History request block numbers are fed into this channel
|
histCh chan []uint64 // History request block numbers are fed into this channel
|
||||||
|
|
||||||
headSub event.Subscription
|
headSub event.Subscription
|
||||||
txSub event.Subscription
|
txSub event.Subscription
|
||||||
|
newPayloadSub event.Subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
// connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
|
// connWrapper is a wrapper to prevent concurrent-write or concurrent-read on the
|
||||||
|
|
@ -198,7 +200,9 @@ func (s *Service) Start() error {
|
||||||
s.headSub = s.backend.SubscribeChainHeadEvent(chainHeadCh)
|
s.headSub = s.backend.SubscribeChainHeadEvent(chainHeadCh)
|
||||||
txEventCh := make(chan core.NewTxsEvent, txChanSize)
|
txEventCh := make(chan core.NewTxsEvent, txChanSize)
|
||||||
s.txSub = s.backend.SubscribeNewTxsEvent(txEventCh)
|
s.txSub = s.backend.SubscribeNewTxsEvent(txEventCh)
|
||||||
go s.loop(chainHeadCh, txEventCh)
|
newPayloadCh := make(chan core.NewPayloadEvent, chainHeadChanSize)
|
||||||
|
s.newPayloadSub = s.backend.SubscribeNewPayloadEvent(newPayloadCh)
|
||||||
|
go s.loop(chainHeadCh, txEventCh, newPayloadCh)
|
||||||
|
|
||||||
log.Info("Stats daemon started")
|
log.Info("Stats daemon started")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -208,18 +212,20 @@ func (s *Service) Start() error {
|
||||||
func (s *Service) Stop() error {
|
func (s *Service) Stop() error {
|
||||||
s.headSub.Unsubscribe()
|
s.headSub.Unsubscribe()
|
||||||
s.txSub.Unsubscribe()
|
s.txSub.Unsubscribe()
|
||||||
|
s.newPayloadSub.Unsubscribe()
|
||||||
log.Info("Stats daemon stopped")
|
log.Info("Stats daemon stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop keeps trying to connect to the netstats server, reporting chain events
|
// loop keeps trying to connect to the netstats server, reporting chain events
|
||||||
// until termination.
|
// until termination.
|
||||||
func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core.NewTxsEvent) {
|
func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core.NewTxsEvent, newPayloadCh chan core.NewPayloadEvent) {
|
||||||
// Start a goroutine that exhausts the subscriptions to avoid events piling up
|
// Start a goroutine that exhausts the subscriptions to avoid events piling up
|
||||||
var (
|
var (
|
||||||
quitCh = make(chan struct{})
|
quitCh = make(chan struct{})
|
||||||
headCh = make(chan *types.Header, 1)
|
headCh = make(chan *types.Header, 1)
|
||||||
txCh = make(chan struct{}, 1)
|
txCh = make(chan struct{}, 1)
|
||||||
|
newPayloadEvCh = make(chan core.NewPayloadEvent, 1)
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
var lastTx mclock.AbsTime
|
var lastTx mclock.AbsTime
|
||||||
|
|
@ -246,11 +252,20 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Notify of new payload events, but drop if too frequent
|
||||||
|
case ev := <-newPayloadCh:
|
||||||
|
select {
|
||||||
|
case newPayloadEvCh <- ev:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// node stopped
|
// node stopped
|
||||||
case <-s.txSub.Err():
|
case <-s.txSub.Err():
|
||||||
break HandleLoop
|
break HandleLoop
|
||||||
case <-s.headSub.Err():
|
case <-s.headSub.Err():
|
||||||
break HandleLoop
|
break HandleLoop
|
||||||
|
case <-s.newPayloadSub.Err():
|
||||||
|
break HandleLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(quitCh)
|
close(quitCh)
|
||||||
|
|
@ -336,6 +351,10 @@ func (s *Service) loop(chainHeadCh chan core.ChainHeadEvent, txEventCh chan core
|
||||||
if err = s.reportPending(conn); err != nil {
|
if err = s.reportPending(conn); err != nil {
|
||||||
log.Warn("Post-block transaction stats report failed", "err", err)
|
log.Warn("Post-block transaction stats report failed", "err", err)
|
||||||
}
|
}
|
||||||
|
case ev := <-newPayloadEvCh:
|
||||||
|
if err = s.reportNewPayload(conn, ev); err != nil {
|
||||||
|
log.Warn("New payload stats report failed", "err", err)
|
||||||
|
}
|
||||||
case <-txCh:
|
case <-txCh:
|
||||||
if err = s.reportPending(conn); err != nil {
|
if err = s.reportPending(conn); err != nil {
|
||||||
log.Warn("Transaction stats report failed", "err", err)
|
log.Warn("Transaction stats report failed", "err", err)
|
||||||
|
|
@ -600,6 +619,33 @@ func (s uncleStats) MarshalJSON() ([]byte, error) {
|
||||||
return []byte("[]"), nil
|
return []byte("[]"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newPayloadStats is the information to report about new payload events.
|
||||||
|
type newPayloadStats struct {
|
||||||
|
Number uint64 `json:"number"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
|
ProcessingTime uint64 `json:"processingTime"` // nanoseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// reportNewPayload reports a new payload event to the stats server.
|
||||||
|
func (s *Service) reportNewPayload(conn *connWrapper, ev core.NewPayloadEvent) error {
|
||||||
|
details := &newPayloadStats{
|
||||||
|
Number: ev.Number,
|
||||||
|
Hash: ev.Hash,
|
||||||
|
ProcessingTime: uint64(ev.ProcessingTime.Nanoseconds()),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("Sending new payload to ethstats", "number", details.Number, "hash", details.Hash)
|
||||||
|
|
||||||
|
stats := map[string]interface{}{
|
||||||
|
"id": s.node,
|
||||||
|
"block": details,
|
||||||
|
}
|
||||||
|
report := map[string][]interface{}{
|
||||||
|
"emit": {"block_new_payload", stats},
|
||||||
|
}
|
||||||
|
return conn.WriteJSON(report)
|
||||||
|
}
|
||||||
|
|
||||||
// reportBlock retrieves the current chain head and reports it to the stats server.
|
// reportBlock retrieves the current chain head and reports it to the stats server.
|
||||||
func (s *Service) reportBlock(conn *connWrapper, header *types.Header) error {
|
func (s *Service) reportBlock(conn *connWrapper, header *types.Header) error {
|
||||||
// Gather the block details from the header or block chain
|
// Gather the block details from the header or block chain
|
||||||
|
|
|
||||||
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=
|
||||||
|
|
|
||||||
|
|
@ -707,6 +707,9 @@ func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if b.header == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
if b.hash == (common.Hash{}) {
|
if b.hash == (common.Hash{}) {
|
||||||
b.hash = b.header.Hash()
|
b.hash = b.header.Hash()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import (
|
||||||
|
|
||||||
type kv struct {
|
type kv struct {
|
||||||
k, v []byte
|
k, v []byte
|
||||||
t bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fuzzer struct {
|
type fuzzer struct {
|
||||||
|
|
@ -62,8 +61,8 @@ func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) {
|
||||||
size := f.readInt()
|
size := f.readInt()
|
||||||
// Fill it with some fluff
|
// Fill it with some fluff
|
||||||
for i := byte(0); i < byte(size); i++ {
|
for i := byte(0); i < byte(size); i++ {
|
||||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false}
|
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}}
|
||||||
value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false}
|
value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}}
|
||||||
trie.MustUpdate(value.k, value.v)
|
trie.MustUpdate(value.k, value.v)
|
||||||
trie.MustUpdate(value2.k, value2.v)
|
trie.MustUpdate(value2.k, value2.v)
|
||||||
vals[string(value.k)] = value
|
vals[string(value.k)] = value
|
||||||
|
|
@ -76,7 +75,7 @@ func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) {
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
k := f.randBytes(32)
|
k := f.randBytes(32)
|
||||||
v := f.randBytes(20)
|
v := f.randBytes(20)
|
||||||
value := &kv{k, v, false}
|
value := &kv{k, v}
|
||||||
trie.MustUpdate(k, v)
|
trie.MustUpdate(k, v)
|
||||||
vals[string(k)] = value
|
vals[string(k)] = value
|
||||||
if f.exhausted {
|
if f.exhausted {
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -148,6 +148,7 @@ func (g *generator) stop() {
|
||||||
g.abort <- ch
|
g.abort <- ch
|
||||||
<-ch
|
<-ch
|
||||||
g.running = false
|
g.running = false
|
||||||
|
log.Debug("Snapshot generation has been terminated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// completed returns the flag indicating if the whole generation is done.
|
// completed returns the flag indicating if the whole generation is done.
|
||||||
|
|
|
||||||
|
|
@ -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