mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
Merge branch 'master' into bs/cell-blobpool/ver-1
This commit is contained in:
commit
c8e5daf85a
227 changed files with 25001 additions and 12652 deletions
6
.github/workflows/go.yml
vendored
6
.github/workflows/go.yml
vendored
|
|
@ -10,7 +10,7 @@ on:
|
|||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: self-hosted-ghr
|
||||
runs-on: [self-hosted-ghr, size-s-x64]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
test:
|
||||
name: Test
|
||||
needs: lint
|
||||
runs-on: self-hosted-ghr
|
||||
runs-on: [self-hosted-ghr, size-l-x64]
|
||||
strategy:
|
||||
matrix:
|
||||
go:
|
||||
|
|
@ -55,4 +55,4 @@ jobs:
|
|||
cache: false
|
||||
|
||||
- name: Run tests
|
||||
run: go run build/ci.go test
|
||||
run: go run build/ci.go test -p 8
|
||||
|
|
|
|||
|
|
@ -485,13 +485,13 @@ var bindTests = []struct {
|
|||
contract Defaulter {
|
||||
address public caller;
|
||||
|
||||
function() {
|
||||
fallback() external payable {
|
||||
caller = msg.sender;
|
||||
}
|
||||
}
|
||||
`,
|
||||
[]string{`6060604052606a8060106000396000f360606040523615601d5760e060020a6000350463fc9c8d3981146040575b605e6000805473ffffffffffffffffffffffffffffffffffffffff191633179055565b606060005473ffffffffffffffffffffffffffffffffffffffff1681565b005b6060908152602090f3`},
|
||||
[]string{`[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`},
|
||||
[]string{`608060405234801561000f575f80fd5b5061013d8061001d5f395ff3fe608060405260043610610021575f3560e01c8063fc9c8d391461006257610022565b5b335f806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055005b34801561006d575f80fd5b5061007661008c565b60405161008391906100ee565b60405180910390f35b5f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100d8826100af565b9050919050565b6100e8816100ce565b82525050565b5f6020820190506101015f8301846100df565b9291505056fea26469706673582212201e9273ecfb1f534644c77f09a25c21baaba81cf1c444ebc071e12a225a23c72964736f6c63430008140033`},
|
||||
[]string{`[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"caller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]`},
|
||||
`
|
||||
"math/big"
|
||||
|
||||
|
|
|
|||
|
|
@ -277,8 +277,10 @@ func (c *BoundContract) RawCreationTransact(opts *TransactOpts, calldata []byte)
|
|||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
||||
// todo(rjl493456442) check the payable fallback or receive is defined
|
||||
// or not, reject invalid transaction at the first place
|
||||
// Check if payable fallback or receive is defined
|
||||
if !c.abi.HasReceive() && !(c.abi.HasFallback() && c.abi.Fallback.IsPayable()) {
|
||||
return nil, fmt.Errorf("contract does not have a payable fallback or receive function")
|
||||
}
|
||||
return c.transact(opts, &c.address, nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,22 +100,29 @@ func TestWaitDeployed(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestWaitDeployedCornerCases(t *testing.T) {
|
||||
backend := simulated.NewBackend(
|
||||
types.GenesisAlloc{
|
||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
||||
},
|
||||
var (
|
||||
backend = simulated.NewBackend(
|
||||
types.GenesisAlloc{
|
||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
|
||||
},
|
||||
)
|
||||
head, _ = backend.Client().HeaderByNumber(t.Context(), nil) // Should be child's, good enough
|
||||
gasPrice = new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||
signer = types.LatestSigner(params.AllDevChainProtocolChanges)
|
||||
code = common.FromHex("6060604052600a8060106000396000f360606040526008565b00")
|
||||
ctx, cancel = context.WithCancel(t.Context())
|
||||
)
|
||||
defer backend.Close()
|
||||
|
||||
head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough
|
||||
gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1))
|
||||
|
||||
// Create a transaction to an account.
|
||||
code := "6060604052600a8060106000396000f360606040526008565b00"
|
||||
tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
// 1. WaitDeploy on a transaction that does not deploy a contract, verify it
|
||||
// returns an error.
|
||||
tx := types.MustSignNewTx(testKey, signer, &types.LegacyTx{
|
||||
Nonce: 0,
|
||||
To: &common.Address{0x01},
|
||||
Gas: 300000,
|
||||
GasPrice: gasPrice,
|
||||
Data: code,
|
||||
})
|
||||
if err := backend.Client().SendTransaction(ctx, tx); err != nil {
|
||||
t.Errorf("failed to send transaction: %q", err)
|
||||
}
|
||||
|
|
@ -124,14 +131,23 @@ func TestWaitDeployedCornerCases(t *testing.T) {
|
|||
t.Errorf("error mismatch: want %q, got %q, ", bind.ErrNoAddressInReceipt, err)
|
||||
}
|
||||
|
||||
// Create a transaction that is not mined.
|
||||
tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code))
|
||||
tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey)
|
||||
// 2. Create a contract, but cancel the WaitDeploy before it is mined.
|
||||
tx = types.MustSignNewTx(testKey, signer, &types.LegacyTx{
|
||||
Nonce: 1,
|
||||
Gas: 300000,
|
||||
GasPrice: gasPrice,
|
||||
Data: code,
|
||||
})
|
||||
|
||||
// Wait in another thread so that we can quickly cancel it after submitting
|
||||
// the transaction.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
contextCanceled := errors.New("context canceled")
|
||||
if _, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash()); err.Error() != contextCanceled.Error() {
|
||||
t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err)
|
||||
defer close(done)
|
||||
want := errors.New("context canceled")
|
||||
_, err := bind.WaitDeployed(ctx, backend.Client(), tx.Hash())
|
||||
if err == nil || errors.Is(want, err) {
|
||||
t.Errorf("error mismatch: want %v, got %v", want, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
@ -139,4 +155,11 @@ func TestWaitDeployedCornerCases(t *testing.T) {
|
|||
t.Errorf("failed to send transaction: %q", err)
|
||||
}
|
||||
cancel()
|
||||
|
||||
// Wait for goroutine to exit or for a timeout.
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second * 2):
|
||||
t.Fatalf("failed to cancel wait deploy")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,9 +99,10 @@ func (ks *KeyStore) init(keydir string) {
|
|||
// TODO: In order for this finalizer to work, there must be no references
|
||||
// to ks. addressCache doesn't keep a reference but unlocked keys do,
|
||||
// so the finalizer will not trigger until all timed unlocks have expired.
|
||||
runtime.SetFinalizer(ks, func(m *KeyStore) {
|
||||
m.cache.close()
|
||||
})
|
||||
runtime.AddCleanup(ks, func(c *accountCache) {
|
||||
c.close()
|
||||
}, ks.cache)
|
||||
|
||||
// Create the initial list of wallets from the cache
|
||||
accs := ks.cache.accounts()
|
||||
ks.wallets = make([]accounts.Wallet, len(accs))
|
||||
|
|
@ -195,11 +196,14 @@ func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscripti
|
|||
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
||||
// is not running).
|
||||
func (ks *KeyStore) updater() {
|
||||
ticker := time.NewTicker(walletRefreshCycle)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
// Wait for an account update or a refresh timeout
|
||||
select {
|
||||
case <-ks.changes:
|
||||
case <-time.After(walletRefreshCycle):
|
||||
case <-ticker.C:
|
||||
}
|
||||
// Run the wallet refresher
|
||||
ks.refreshWallets()
|
||||
|
|
|
|||
22
appveyor.yml
22
appveyor.yml
|
|
@ -2,7 +2,6 @@ clone_depth: 5
|
|||
version: "{branch}.{build}"
|
||||
|
||||
image:
|
||||
- Ubuntu
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
|
|
@ -17,25 +16,6 @@ install:
|
|||
- go version
|
||||
|
||||
for:
|
||||
# Linux has its own script without -arch and -cc.
|
||||
# The linux builder also runs lint.
|
||||
- matrix:
|
||||
only:
|
||||
- image: Ubuntu
|
||||
build_script:
|
||||
- go run build/ci.go lint
|
||||
- go run build/ci.go check_generate
|
||||
- go run build/ci.go check_baddeps
|
||||
- go run build/ci.go install -dlgo
|
||||
test_script:
|
||||
- go run build/ci.go test -dlgo -short
|
||||
|
||||
# linux/386 is disabled.
|
||||
- matrix:
|
||||
exclude:
|
||||
- image: Ubuntu
|
||||
GETH_ARCH: 386
|
||||
|
||||
# Windows builds for amd64 + 386.
|
||||
- matrix:
|
||||
only:
|
||||
|
|
@ -56,4 +36,4 @@ for:
|
|||
- go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
- go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
|
||||
test_script:
|
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short
|
||||
- go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short -skip-spectests
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ var (
|
|||
testServer2 = testServer("testServer2")
|
||||
|
||||
testBlock1 = types.NewBeaconBlock(&deneb.BeaconBlock{
|
||||
Slot: 123,
|
||||
Slot: 127,
|
||||
Body: deneb.BeaconBlockBody{
|
||||
ExecutionPayload: deneb.ExecutionPayload{
|
||||
BlockNumber: 456,
|
||||
|
|
@ -41,7 +41,7 @@ var (
|
|||
},
|
||||
})
|
||||
testBlock2 = types.NewBeaconBlock(&deneb.BeaconBlock{
|
||||
Slot: 124,
|
||||
Slot: 128,
|
||||
Body: deneb.BeaconBlockBody{
|
||||
ExecutionPayload: deneb.ExecutionPayload{
|
||||
BlockNumber: 457,
|
||||
|
|
@ -49,6 +49,14 @@ var (
|
|||
},
|
||||
},
|
||||
})
|
||||
testFinal1 = types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{
|
||||
BlockNumber: 395,
|
||||
BlockHash: zrntcommon.Hash32(common.HexToHash("abbe7625624bf8ddd84723709e2758956289465dd23475f02387e0854942666")),
|
||||
})
|
||||
testFinal2 = types.NewExecutionHeader(&deneb.ExecutionPayloadHeader{
|
||||
BlockNumber: 420,
|
||||
BlockHash: zrntcommon.Hash32(common.HexToHash("9182a6ef8723654de174283750932ccc092378549836bf4873657eeec474598")),
|
||||
})
|
||||
)
|
||||
|
||||
type testServer string
|
||||
|
|
@ -66,9 +74,10 @@ func TestBlockSync(t *testing.T) {
|
|||
ts.AddServer(testServer1, 1)
|
||||
ts.AddServer(testServer2, 1)
|
||||
|
||||
expHeadBlock := func(expHead *types.BeaconBlock) {
|
||||
expHeadEvent := func(expHead *types.BeaconBlock, expFinal *types.ExecutionHeader) {
|
||||
t.Helper()
|
||||
var expNumber, headNumber uint64
|
||||
var expFinalHash, finalHash common.Hash
|
||||
if expHead != nil {
|
||||
p, err := expHead.ExecutionPayload()
|
||||
if err != nil {
|
||||
|
|
@ -76,19 +85,26 @@ func TestBlockSync(t *testing.T) {
|
|||
}
|
||||
expNumber = p.NumberU64()
|
||||
}
|
||||
if expFinal != nil {
|
||||
expFinalHash = expFinal.BlockHash()
|
||||
}
|
||||
select {
|
||||
case event := <-headCh:
|
||||
headNumber = event.Block.NumberU64()
|
||||
finalHash = event.Finalized
|
||||
default:
|
||||
}
|
||||
if headNumber != expNumber {
|
||||
t.Errorf("Wrong head block, expected block number %d, got %d)", expNumber, headNumber)
|
||||
}
|
||||
if finalHash != expFinalHash {
|
||||
t.Errorf("Wrong finalized block, expected block hash %064x, got %064x)", expFinalHash[:], finalHash[:])
|
||||
}
|
||||
}
|
||||
|
||||
// no block requests expected until head tracker knows about a head
|
||||
ts.Run(1)
|
||||
expHeadBlock(nil)
|
||||
expHeadEvent(nil, nil)
|
||||
|
||||
// set block 1 as prefetch head, announced by server 2
|
||||
head1 := blockHeadInfo(testBlock1)
|
||||
|
|
@ -103,12 +119,13 @@ func TestBlockSync(t *testing.T) {
|
|||
ts.AddAllowance(testServer2, 1)
|
||||
ts.Run(3)
|
||||
// head block still not expected as the fetched block is not the validated head yet
|
||||
expHeadBlock(nil)
|
||||
expHeadEvent(nil, nil)
|
||||
|
||||
// set as validated head, expect no further requests but block 1 set as head block
|
||||
ht.validated.Header = testBlock1.Header()
|
||||
ht.finalized, ht.finalizedPayload = testBlock1.Header(), testFinal1
|
||||
ts.Run(4)
|
||||
expHeadBlock(testBlock1)
|
||||
expHeadEvent(testBlock1, testFinal1)
|
||||
|
||||
// set block 2 as prefetch head, announced by server 1
|
||||
head2 := blockHeadInfo(testBlock2)
|
||||
|
|
@ -126,17 +143,26 @@ func TestBlockSync(t *testing.T) {
|
|||
// expect req2 retry to server 2
|
||||
ts.Run(7, testServer2, sync.ReqBeaconBlock(head2.BlockRoot))
|
||||
// now head block should be unavailable again
|
||||
expHeadBlock(nil)
|
||||
expHeadEvent(nil, nil)
|
||||
|
||||
// valid response, now head block should be block 2 immediately as it is already validated
|
||||
// but head event is still not expected because an epoch boundary was crossed and the
|
||||
// expected finality update has not arrived yet
|
||||
ts.RequestEvent(request.EvResponse, ts.Request(7, 1), testBlock2)
|
||||
ts.Run(8)
|
||||
expHeadBlock(testBlock2)
|
||||
expHeadEvent(nil, nil)
|
||||
|
||||
// expected finality update arrived, now a head event is expected
|
||||
ht.finalized, ht.finalizedPayload = testBlock2.Header(), testFinal2
|
||||
ts.Run(9)
|
||||
expHeadEvent(testBlock2, testFinal2)
|
||||
}
|
||||
|
||||
type testHeadTracker struct {
|
||||
prefetch types.HeadInfo
|
||||
validated types.SignedHeader
|
||||
prefetch types.HeadInfo
|
||||
validated types.SignedHeader
|
||||
finalized types.Header
|
||||
finalizedPayload *types.ExecutionHeader
|
||||
}
|
||||
|
||||
func (h *testHeadTracker) PrefetchHead() types.HeadInfo {
|
||||
|
|
@ -151,13 +177,14 @@ func (h *testHeadTracker) ValidatedOptimistic() (types.OptimisticUpdate, bool) {
|
|||
}, h.validated.Header != (types.Header{})
|
||||
}
|
||||
|
||||
// TODO add test case for finality
|
||||
func (h *testHeadTracker) ValidatedFinality() (types.FinalityUpdate, bool) {
|
||||
finalized := types.NewExecutionHeader(new(deneb.ExecutionPayloadHeader))
|
||||
if h.validated.Header == (types.Header{}) || h.finalizedPayload == nil {
|
||||
return types.FinalityUpdate{}, false
|
||||
}
|
||||
return types.FinalityUpdate{
|
||||
Attested: types.HeaderWithExecProof{Header: h.validated.Header},
|
||||
Finalized: types.HeaderWithExecProof{PayloadHeader: finalized},
|
||||
Attested: types.HeaderWithExecProof{Header: h.finalized},
|
||||
Finalized: types.HeaderWithExecProof{Header: h.finalized, PayloadHeader: h.finalizedPayload},
|
||||
Signature: h.validated.Signature,
|
||||
SignatureSlot: h.validated.SignatureSlot,
|
||||
}, h.validated.Header != (types.Header{})
|
||||
}, true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
|
|||
type ExecutionPayloadEnvelope struct {
|
||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||
BlobsBundle *BlobsBundle `json:"blobsBundle"`
|
||||
Requests []hexutil.Bytes `json:"executionRequests"`
|
||||
Override bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness,omitempty"`
|
||||
|
|
@ -42,7 +42,7 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
|
|||
type ExecutionPayloadEnvelope struct {
|
||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
|
||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||
BlobsBundle *BlobsBundle `json:"blobsBundle"`
|
||||
Requests []hexutil.Bytes `json:"executionRequests"`
|
||||
Override *bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness,omitempty"`
|
||||
|
|
|
|||
|
|
@ -33,8 +33,22 @@ import (
|
|||
type PayloadVersion byte
|
||||
|
||||
var (
|
||||
// PayloadV1 is the identifier of ExecutionPayloadV1 introduced in paris fork.
|
||||
// https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#executionpayloadv1
|
||||
PayloadV1 PayloadVersion = 0x1
|
||||
|
||||
// PayloadV2 is the identifier of ExecutionPayloadV2 introduced in shanghai fork.
|
||||
//
|
||||
// https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#executionpayloadv2
|
||||
// ExecutionPayloadV2 has the syntax of ExecutionPayloadV1 and appends a
|
||||
// single field: withdrawals.
|
||||
PayloadV2 PayloadVersion = 0x2
|
||||
|
||||
// PayloadV3 is the identifier of ExecutionPayloadV3 introduced in cancun fork.
|
||||
//
|
||||
// https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#executionpayloadv3
|
||||
// ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new
|
||||
// fields: blobGasUsed and excessBlobGas.
|
||||
PayloadV3 PayloadVersion = 0x3
|
||||
)
|
||||
|
||||
|
|
@ -106,13 +120,18 @@ type StatelessPayloadStatusV1 struct {
|
|||
type ExecutionPayloadEnvelope struct {
|
||||
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
|
||||
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
|
||||
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
|
||||
BlobsBundle *BlobsBundle `json:"blobsBundle"`
|
||||
Requests [][]byte `json:"executionRequests"`
|
||||
Override bool `json:"shouldOverrideBuilder"`
|
||||
Witness *hexutil.Bytes `json:"witness,omitempty"`
|
||||
}
|
||||
|
||||
type BlobsBundleV1 struct {
|
||||
// BlobsBundle includes the marshalled sidecar data. Note this structure is
|
||||
// shared by BlobsBundleV1 and BlobsBundleV2 for the sake of simplicity.
|
||||
//
|
||||
// - BlobsBundleV1: proofs contain exactly len(blobs) kzg proofs.
|
||||
// - BlobsBundleV2: proofs contain exactly CELLS_PER_EXT_BLOB * len(blobs) cell proofs.
|
||||
type BlobsBundle struct {
|
||||
Commitments []hexutil.Bytes `json:"commitments"`
|
||||
Proofs []hexutil.Bytes `json:"proofs"`
|
||||
Blobs []hexutil.Bytes `json:"blobs"`
|
||||
|
|
@ -125,7 +144,7 @@ type BlobAndProofV1 struct {
|
|||
|
||||
type BlobAndProofV2 struct {
|
||||
Blob hexutil.Bytes `json:"blob"`
|
||||
CellProofs []hexutil.Bytes `json:"proofs"`
|
||||
CellProofs []hexutil.Bytes `json:"proofs"` // proofs MUST contain exactly CELLS_PER_EXT_BLOB cell proofs.
|
||||
}
|
||||
|
||||
// JSON type overrides for ExecutionPayloadEnvelope.
|
||||
|
|
@ -327,18 +346,27 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
|
|||
}
|
||||
|
||||
// Add blobs.
|
||||
bundle := BlobsBundleV1{
|
||||
bundle := BlobsBundle{
|
||||
Commitments: make([]hexutil.Bytes, 0),
|
||||
Blobs: make([]hexutil.Bytes, 0),
|
||||
Proofs: make([]hexutil.Bytes, 0),
|
||||
}
|
||||
for _, sidecar := range sidecars {
|
||||
for j := range sidecar.Blobs {
|
||||
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
|
||||
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
|
||||
bundle.Blobs = append(bundle.Blobs, sidecar.Blobs[j][:])
|
||||
bundle.Commitments = append(bundle.Commitments, sidecar.Commitments[j][:])
|
||||
}
|
||||
// - Before the Osaka fork, only version-0 blob transactions should be packed,
|
||||
// with the proof length equal to len(blobs).
|
||||
//
|
||||
// - After the Osaka fork, only version-1 blob transactions should be packed,
|
||||
// with the proof length equal to CELLS_PER_EXT_BLOB * len(blobs).
|
||||
//
|
||||
// Ideally, length validation should be performed based on the bundle version.
|
||||
// In practice, this is unnecessary because blob transaction filtering is
|
||||
// already done during payload construction.
|
||||
for _, proof := range sidecar.Proofs {
|
||||
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:]))
|
||||
bundle.Proofs = append(bundle.Proofs, proof[:])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
|
|
@ -90,12 +91,8 @@ func (c *ChainConfig) AddFork(name string, epoch uint64, version []byte) *ChainC
|
|||
|
||||
// LoadForks parses the beacon chain configuration file (config.yaml) and extracts
|
||||
// the list of forks.
|
||||
func (c *ChainConfig) LoadForks(path string) error {
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read beacon chain config file: %v", err)
|
||||
}
|
||||
config := make(map[string]string)
|
||||
func (c *ChainConfig) LoadForks(file []byte) error {
|
||||
config := make(map[string]any)
|
||||
if err := yaml.Unmarshal(file, &config); err != nil {
|
||||
return fmt.Errorf("failed to parse beacon chain config file: %v", err)
|
||||
}
|
||||
|
|
@ -108,18 +105,36 @@ func (c *ChainConfig) LoadForks(path string) error {
|
|||
for key, value := range config {
|
||||
if strings.HasSuffix(key, "_FORK_VERSION") {
|
||||
name := key[:len(key)-len("_FORK_VERSION")]
|
||||
if v, err := hexutil.Decode(value); err == nil {
|
||||
switch version := value.(type) {
|
||||
case int:
|
||||
versions[name] = new(big.Int).SetUint64(uint64(version)).FillBytes(make([]byte, 4))
|
||||
case uint64:
|
||||
versions[name] = new(big.Int).SetUint64(version).FillBytes(make([]byte, 4))
|
||||
case string:
|
||||
v, err := hexutil.Decode(version)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", version, err)
|
||||
}
|
||||
versions[name] = v
|
||||
} else {
|
||||
return fmt.Errorf("failed to decode hex fork id %q in beacon chain config file: %v", value, err)
|
||||
default:
|
||||
return fmt.Errorf("invalid fork version %q in beacon chain config file", version)
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(key, "_FORK_EPOCH") {
|
||||
name := key[:len(key)-len("_FORK_EPOCH")]
|
||||
if v, err := strconv.ParseUint(value, 10, 64); err == nil {
|
||||
switch epoch := value.(type) {
|
||||
case int:
|
||||
epochs[name] = uint64(epoch)
|
||||
case uint64:
|
||||
epochs[name] = epoch
|
||||
case string:
|
||||
v, err := strconv.ParseUint(epoch, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", epoch, err)
|
||||
}
|
||||
epochs[name] = v
|
||||
} else {
|
||||
return fmt.Errorf("failed to parse epoch number %q in beacon chain config file: %v", value, err)
|
||||
default:
|
||||
return fmt.Errorf("invalid fork epoch %q in beacon chain config file", epoch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
beacon/params/config_test.go
Normal file
34
beacon/params/config_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChainConfig_LoadForks(t *testing.T) {
|
||||
const config = `
|
||||
GENESIS_FORK_VERSION: 0x00000000
|
||||
|
||||
ALTAIR_FORK_VERSION: 0x00000001
|
||||
ALTAIR_FORK_EPOCH: 1
|
||||
|
||||
EIP7928_FORK_VERSION: 0xb0000038
|
||||
EIP7928_FORK_EPOCH: 18446744073709551615
|
||||
|
||||
BLOB_SCHEDULE: []
|
||||
`
|
||||
c := &ChainConfig{}
|
||||
err := c.LoadForks([]byte(config))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, fork := range c.Forks {
|
||||
if fork.Name == "GENESIS" && (fork.Epoch != 0) {
|
||||
t.Errorf("unexpected genesis fork epoch %d", fork.Epoch)
|
||||
}
|
||||
if fork.Name == "ALTAIR" && (fork.Epoch != 1 || !bytes.Equal(fork.Version, []byte{0, 0, 0, 1})) {
|
||||
t.Errorf("unexpected altair fork epoch %d version %x", fork.Epoch, fork.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# This file contains sha256 checksums of optional build dependencies.
|
||||
|
||||
# version:spec-tests fusaka-devnet-3%40v1.0.0
|
||||
# version:spec-tests v5.1.0
|
||||
# https://github.com/ethereum/execution-spec-tests/releases
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/fusaka-devnet-3%40v1.0.0
|
||||
576261e1280e5300c458aa9b05eccb2fec5ff80a0005940dc52fa03fdd907249 fixtures_fusaka-devnet-3.tar.gz
|
||||
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
|
||||
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
|
||||
|
||||
# version:golang 1.25.1
|
||||
# https://go.dev/dl/
|
||||
|
|
|
|||
170
build/ci.go
170
build/ci.go
|
|
@ -57,6 +57,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cespare/cp"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto/signify"
|
||||
"github.com/ethereum/go-ethereum/internal/build"
|
||||
"github.com/ethereum/go-ethereum/internal/download"
|
||||
|
|
@ -64,6 +65,11 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
goModules = []string{
|
||||
".",
|
||||
"./cmd/keeper",
|
||||
}
|
||||
|
||||
// Files that end up in the geth*.zip archive.
|
||||
gethArchiveFiles = []string{
|
||||
"COPYING",
|
||||
|
|
@ -143,7 +149,7 @@ func executablePath(name string) string {
|
|||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
if !build.FileExist(filepath.Join("build", "ci.go")) {
|
||||
if !common.FileExist(filepath.Join("build", "ci.go")) {
|
||||
log.Fatal("this script must be run from the root of the repository")
|
||||
}
|
||||
if len(os.Args) < 2 {
|
||||
|
|
@ -216,7 +222,7 @@ func doInstall(cmdline []string) {
|
|||
// Default: collect all 'main' packages in cmd/ and build those.
|
||||
packages := flag.Args()
|
||||
if len(packages) == 0 {
|
||||
packages = build.FindMainPackages("./cmd")
|
||||
packages = build.FindMainPackages(&tc, "./cmd/...")
|
||||
}
|
||||
|
||||
// Do the build!
|
||||
|
|
@ -275,26 +281,33 @@ func buildFlags(env build.Environment, staticLinking bool, buildTags []string) (
|
|||
|
||||
func doTest(cmdline []string) {
|
||||
var (
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
arch = flag.String("arch", "", "Run tests for given architecture")
|
||||
cc = flag.String("cc", "", "Sets C compiler binary")
|
||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||
verbose = flag.Bool("v", false, "Whether to log verbosely")
|
||||
race = flag.Bool("race", false, "Execute the race detector")
|
||||
short = flag.Bool("short", false, "Pass the 'short'-flag to go test")
|
||||
cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads")
|
||||
dlgo = flag.Bool("dlgo", false, "Download Go and build with it")
|
||||
arch = flag.String("arch", "", "Run tests for given architecture")
|
||||
cc = flag.String("cc", "", "Sets C compiler binary")
|
||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||
verbose = flag.Bool("v", false, "Whether to log verbosely")
|
||||
race = flag.Bool("race", false, "Execute the race detector")
|
||||
short = flag.Bool("short", false, "Pass the 'short'-flag to go test")
|
||||
cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads")
|
||||
skipspectests = flag.Bool("skip-spectests", false, "Skip downloading execution-spec-tests fixtures")
|
||||
threads = flag.Int("p", 1, "Number of CPU threads to use for testing")
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
||||
// Get test fixtures.
|
||||
// Load checksums file (needed for both spec tests and dlgo)
|
||||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||
downloadSpecTestFixtures(csdb, *cachedir)
|
||||
|
||||
// Get test fixtures.
|
||||
if !*skipspectests {
|
||||
downloadSpecTestFixtures(csdb, *cachedir)
|
||||
}
|
||||
|
||||
// Configure the toolchain.
|
||||
tc := build.GoToolchain{GOARCH: *arch, CC: *cc}
|
||||
if *dlgo {
|
||||
tc.Root = build.DownloadGo(csdb)
|
||||
}
|
||||
|
||||
gotest := tc.Go("test")
|
||||
|
||||
// CI needs a bit more time for the statetests (default 45m).
|
||||
|
|
@ -308,7 +321,7 @@ func doTest(cmdline []string) {
|
|||
|
||||
// Test a single package at a time. CI builders are slow
|
||||
// and some tests run into timeouts under load.
|
||||
gotest.Args = append(gotest.Args, "-p", "1")
|
||||
gotest.Args = append(gotest.Args, "-p", fmt.Sprintf("%d", *threads))
|
||||
if *coverage {
|
||||
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
||||
}
|
||||
|
|
@ -323,17 +336,25 @@ func doTest(cmdline []string) {
|
|||
}
|
||||
|
||||
packages := flag.CommandLine.Args()
|
||||
if len(packages) == 0 {
|
||||
packages = workspacePackagePatterns()
|
||||
if len(packages) > 0 {
|
||||
gotest.Args = append(gotest.Args, packages...)
|
||||
build.MustRun(gotest)
|
||||
return
|
||||
}
|
||||
|
||||
// No packages specified, run all tests for all modules.
|
||||
gotest.Args = append(gotest.Args, "./...")
|
||||
for _, mod := range goModules {
|
||||
test := *gotest
|
||||
test.Dir = mod
|
||||
build.MustRun(&test)
|
||||
}
|
||||
gotest.Args = append(gotest.Args, packages...)
|
||||
build.MustRun(gotest)
|
||||
}
|
||||
|
||||
// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures.
|
||||
func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
|
||||
ext := ".tar.gz"
|
||||
base := "fixtures_fusaka-devnet-3"
|
||||
base := "fixtures_develop"
|
||||
archivePath := filepath.Join(cachedir, base+ext)
|
||||
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
|
||||
log.Fatal(err)
|
||||
|
|
@ -351,40 +372,46 @@ func doCheckGenerate() {
|
|||
cachedir = flag.String("cachedir", "./build/cache", "directory for caching binaries.")
|
||||
tc = new(build.GoToolchain)
|
||||
)
|
||||
// Compute the origin hashes of all the files
|
||||
var hashes map[string][32]byte
|
||||
|
||||
var err error
|
||||
hashes, err = build.HashFolder(".", []string{"tests/testdata", "build/cache", ".git"})
|
||||
if err != nil {
|
||||
log.Fatal("Error computing hashes", "err", err)
|
||||
}
|
||||
// Run any go generate steps we might be missing
|
||||
var (
|
||||
protocPath = downloadProtoc(*cachedir)
|
||||
protocGenGoPath = downloadProtocGenGo(*cachedir)
|
||||
)
|
||||
c := tc.Go("generate", workspacePackagePatterns()...)
|
||||
pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")}
|
||||
c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator)))
|
||||
build.MustRun(c)
|
||||
|
||||
// Check if generate file hashes have changed
|
||||
generated, err := build.HashFolder(".", []string{"tests/testdata", "build/cache", ".git"})
|
||||
if err != nil {
|
||||
log.Fatalf("Error re-computing hashes: %v", err)
|
||||
}
|
||||
updates := build.DiffHashes(hashes, generated)
|
||||
for _, file := range updates {
|
||||
log.Printf("File changed: %s", file)
|
||||
}
|
||||
if len(updates) != 0 {
|
||||
log.Fatal("One or more generated files were updated by running 'go generate ./...'")
|
||||
for _, mod := range goModules {
|
||||
// Compute the origin hashes of all the files
|
||||
hashes, err := build.HashFolder(mod, []string{"tests/testdata", "build/cache", ".git"})
|
||||
if err != nil {
|
||||
log.Fatal("Error computing hashes", "err", err)
|
||||
}
|
||||
|
||||
c := tc.Go("generate", "./...")
|
||||
c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator)))
|
||||
c.Dir = mod
|
||||
build.MustRun(c)
|
||||
// Check if generate file hashes have changed
|
||||
generated, err := build.HashFolder(mod, []string{"tests/testdata", "build/cache", ".git"})
|
||||
if err != nil {
|
||||
log.Fatalf("Error re-computing hashes: %v", err)
|
||||
}
|
||||
updates := build.DiffHashes(hashes, generated)
|
||||
for _, file := range updates {
|
||||
log.Printf("File changed: %s", file)
|
||||
}
|
||||
if len(updates) != 0 {
|
||||
log.Fatal("One or more generated files were updated by running 'go generate ./...'")
|
||||
}
|
||||
}
|
||||
fmt.Println("No stale files detected.")
|
||||
|
||||
// Run go mod tidy check.
|
||||
build.MustRun(tc.Go("mod", "tidy", "-diff"))
|
||||
for _, mod := range goModules {
|
||||
tidy := tc.Go("mod", "tidy", "-diff")
|
||||
tidy.Dir = mod
|
||||
build.MustRun(tidy)
|
||||
}
|
||||
fmt.Println("No untidy module files detected.")
|
||||
}
|
||||
|
||||
|
|
@ -425,20 +452,29 @@ func doLint(cmdline []string) {
|
|||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
||||
packages := flag.CommandLine.Args()
|
||||
if len(packages) == 0 {
|
||||
// Get module directories in workspace.
|
||||
packages = []string{"./..."}
|
||||
modules := workspaceModules()
|
||||
for _, m := range modules[1:] {
|
||||
dir := strings.TrimPrefix(m, modules[0])
|
||||
packages = append(packages, "."+dir+"/...")
|
||||
}
|
||||
linter := downloadLinter(*cachedir)
|
||||
linter, err := filepath.Abs(linter)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
config, err := filepath.Abs(".golangci.yml")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
linter := downloadLinter(*cachedir)
|
||||
lflags := []string{"run", "--config", ".golangci.yml"}
|
||||
build.MustRunCommandWithOutput(linter, append(lflags, packages...)...)
|
||||
lflags := []string{"run", "--config", config}
|
||||
packages := flag.CommandLine.Args()
|
||||
if len(packages) > 0 {
|
||||
build.MustRunCommandWithOutput(linter, append(lflags, packages...)...)
|
||||
} else {
|
||||
// Run for all modules in workspace.
|
||||
for _, mod := range goModules {
|
||||
args := append(lflags, "./...")
|
||||
lintcmd := exec.Command(linter, args...)
|
||||
lintcmd.Dir = mod
|
||||
build.MustRunWithOutput(lintcmd)
|
||||
}
|
||||
}
|
||||
fmt.Println("You have achieved perfection.")
|
||||
}
|
||||
|
||||
|
|
@ -866,7 +902,7 @@ func ppaUpload(workdir, ppa, sshUser string, files []string) {
|
|||
var idfile string
|
||||
if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 {
|
||||
idfile = filepath.Join(workdir, "sshkey")
|
||||
if !build.FileExist(idfile) {
|
||||
if !common.FileExist(idfile) {
|
||||
os.WriteFile(idfile, sshkey, 0600)
|
||||
}
|
||||
}
|
||||
|
|
@ -1176,31 +1212,3 @@ func doSanityCheck() {
|
|||
csdb := download.MustLoadChecksums("build/checksums.txt")
|
||||
csdb.DownloadAndVerifyAll()
|
||||
}
|
||||
|
||||
// workspaceModules lists the module paths in the current work.
|
||||
func workspaceModules() []string {
|
||||
listing, err := new(build.GoToolchain).Go("list", "-m").Output()
|
||||
if err != nil {
|
||||
log.Fatalf("go list failed:", err)
|
||||
}
|
||||
var modules []string
|
||||
for _, m := range bytes.Split(listing, []byte("\n")) {
|
||||
m = bytes.TrimSpace(m)
|
||||
if len(m) > 0 {
|
||||
modules = append(modules, string(m))
|
||||
}
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
panic("no modules found")
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
func workspacePackagePatterns() []string {
|
||||
modules := workspaceModules()
|
||||
patterns := make([]string, len(modules))
|
||||
for i, m := range modules {
|
||||
patterns[i] = m + "/..."
|
||||
}
|
||||
return patterns
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ with our test chain. The chain files are located in `./cmd/devp2p/internal/ethte
|
|||
--nat=none \
|
||||
--networkid 3503995874084926 \
|
||||
--verbosity 5 \
|
||||
--authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
|
||||
--authrpc.jwtsecret jwt.secret
|
||||
|
||||
Note that the tests also require access to the engine API.
|
||||
The test suite can now be executed using the devp2p tool.
|
||||
|
|
@ -130,7 +130,7 @@ The test suite can now be executed using the devp2p tool.
|
|||
--chain internal/ethtest/testdata \
|
||||
--node enode://.... \
|
||||
--engineapi http://127.0.0.1:8551 \
|
||||
--jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365
|
||||
--jwtsecret $(cat jwt.secret)
|
||||
|
||||
Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
hivechain generate \
|
||||
--pos \
|
||||
--fork-interval 6 \
|
||||
--tx-interval 1 \
|
||||
--length 500 \
|
||||
--length 600 \
|
||||
--outdir testdata \
|
||||
--lastfork cancun \
|
||||
--lastfork prague \
|
||||
--outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv
|
||||
|
|
|
|||
|
|
@ -86,3 +86,9 @@ func protoOffset(proto Proto) uint64 {
|
|||
panic("unhandled protocol")
|
||||
}
|
||||
}
|
||||
|
||||
// msgTypePtr is the constraint for protocol message types.
|
||||
type msgTypePtr[U any] interface {
|
||||
*U
|
||||
Kind() byte
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,9 +86,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
|||
root: root,
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 86,
|
||||
expAccounts: 67,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
||||
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"),
|
||||
desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.",
|
||||
},
|
||||
{
|
||||
|
|
@ -96,9 +96,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
|||
root: root,
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 65,
|
||||
expAccounts: 49,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6"),
|
||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
||||
desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.",
|
||||
},
|
||||
{
|
||||
|
|
@ -106,9 +106,9 @@ func (s *Suite) TestSnapGetAccountRange(t *utesting.T) {
|
|||
root: root,
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 44,
|
||||
expAccounts: 34,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595"),
|
||||
expLast: common.HexToHash("0x2ef46ebd2073cecde499c2e8df028ad79a26d57bfaa812c4c6f7eb4c9617b913"),
|
||||
desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.",
|
||||
},
|
||||
{
|
||||
|
|
@ -177,9 +177,9 @@ The server should return the first available account.`,
|
|||
root: root,
|
||||
startingHash: firstKey,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 86,
|
||||
expAccounts: 67,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"),
|
||||
expLast: common.HexToHash("0x622e662246601dd04f996289ce8b85e86db7bb15bb17f86487ec9d543ddb6f9a"),
|
||||
desc: `In this test, startingHash is exactly the first available account key.
|
||||
The server should return the first available account of the state as the first item.`,
|
||||
},
|
||||
|
|
@ -188,9 +188,9 @@ The server should return the first available account of the state as the first i
|
|||
root: root,
|
||||
startingHash: hashAdd(firstKey, 1),
|
||||
limitHash: ffHash,
|
||||
expAccounts: 86,
|
||||
expAccounts: 67,
|
||||
expFirst: secondKey,
|
||||
expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"),
|
||||
expLast: common.HexToHash("0x66192e4c757fba1cdc776e6737008f42d50370d3cd801db3624274283bf7cd63"),
|
||||
desc: `In this test, startingHash is after the first available key.
|
||||
The server should return the second account of the state as the first item.`,
|
||||
},
|
||||
|
|
@ -226,9 +226,9 @@ server to return no data because genesis is older than 127 blocks.`,
|
|||
root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127),
|
||||
startingHash: zero,
|
||||
limitHash: ffHash,
|
||||
expAccounts: 84,
|
||||
expAccounts: 66,
|
||||
expFirst: firstKey,
|
||||
expLast: common.HexToHash("0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3"),
|
||||
expLast: common.HexToHash("0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e"),
|
||||
desc: `This test requests data at a state root that is 127 blocks old.
|
||||
We expect the server to have this state available.`,
|
||||
},
|
||||
|
|
@ -657,8 +657,8 @@ The server should reject the request.`,
|
|||
// It's a bit unfortunate these are hard-coded, but the result depends on
|
||||
// a lot of aspects of the state trie and can't be guessed in a simple
|
||||
// way. So you'll have to update this when the test chain is changed.
|
||||
common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"),
|
||||
common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"),
|
||||
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"),
|
||||
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"),
|
||||
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
||||
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
||||
empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty,
|
||||
|
|
@ -678,8 +678,8 @@ The server should reject the request.`,
|
|||
// be updated when the test chain is changed.
|
||||
expHashes: []common.Hash{
|
||||
empty,
|
||||
common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"),
|
||||
common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"),
|
||||
common.HexToHash("0x0a76c9812ca90ffed8ee4d191e683f93386b6e50cfe3679c0760d27510aa7fc5"),
|
||||
common.HexToHash("0x5bdc0d6057b35642a16d27223ea5454e5a17a400e28f7328971a5f2a87773b76"),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -196,6 +196,7 @@ to check if the node disconnects after receiving multiple invalid requests.`)
|
|||
func (s *Suite) TestSimultaneousRequests(t *utesting.T) {
|
||||
t.Log(`This test requests blocks headers from the node, performing two requests
|
||||
concurrently, with different request IDs.`)
|
||||
|
||||
conn, err := s.dialAndPeer(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
|
|
@ -235,37 +236,36 @@ concurrently, with different request IDs.`)
|
|||
}
|
||||
|
||||
// Wait for responses.
|
||||
headers1 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil {
|
||||
t.Fatalf("error reading block headers msg: %v", err)
|
||||
}
|
||||
if got, want := headers1.RequestId, req1.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
headers2 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil {
|
||||
t.Fatalf("error reading block headers msg: %v", err)
|
||||
}
|
||||
if got, want := headers2.RequestId, req2.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
// Note they can arrive in either order.
|
||||
resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 {
|
||||
if msg.RequestId != 111 && msg.RequestId != 222 {
|
||||
t.Fatalf("response with unknown request ID: %v", msg.RequestId)
|
||||
}
|
||||
return msg.RequestId
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check received headers for accuracy.
|
||||
// Check if headers match.
|
||||
resp1 := resp[111]
|
||||
if expected, err := s.chain.GetHeaders(req1); err != nil {
|
||||
t.Fatalf("failed to get expected headers for request 1: %v", err)
|
||||
} else if !headersMatch(expected, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1)
|
||||
} else if !headersMatch(expected, resp1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 111, expected, resp1)
|
||||
}
|
||||
resp2 := resp[222]
|
||||
if expected, err := s.chain.GetHeaders(req2); err != nil {
|
||||
t.Fatalf("failed to get expected headers for request 2: %v", err)
|
||||
} else if !headersMatch(expected, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2)
|
||||
} else if !headersMatch(expected, resp2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch for request ID %v: \nexpected %v \ngot %v", 222, expected, resp2)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestSameRequestID(t *utesting.T) {
|
||||
t.Log(`This test requests block headers, performing two concurrent requests with the
|
||||
same request ID. The node should handle the request by responding to both requests.`)
|
||||
|
||||
conn, err := s.dialAndPeer(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("peering failed: %v", err)
|
||||
|
|
@ -289,7 +289,7 @@ same request ID. The node should handle the request by responding to both reques
|
|||
Origin: eth.HashOrNumber{
|
||||
Number: 33,
|
||||
},
|
||||
Amount: 2,
|
||||
Amount: 3,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -301,35 +301,52 @@ same request ID. The node should handle the request by responding to both reques
|
|||
t.Fatalf("failed to write to connection: %v", err)
|
||||
}
|
||||
|
||||
// Wait for the responses.
|
||||
headers1 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
if got, want := headers1.RequestId, request1.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id: got %d, want %d", got, want)
|
||||
}
|
||||
headers2 := new(eth.BlockHeadersPacket)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil {
|
||||
t.Fatalf("error reading from connection: %v", err)
|
||||
}
|
||||
if got, want := headers2.RequestId, request2.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id: got %d, want %d", got, want)
|
||||
// Wait for the responses. They can arrive in either order, and we can't tell them
|
||||
// apart by their request ID, so use the number of headers instead.
|
||||
resp, err := collectResponses(conn, 2, func(msg *eth.BlockHeadersPacket) uint64 {
|
||||
id := uint64(len(msg.BlockHeadersRequest))
|
||||
if id != 2 && id != 3 {
|
||||
t.Fatalf("invalid number of headers in response: %d", id)
|
||||
}
|
||||
return id
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if headers match.
|
||||
resp1 := resp[2]
|
||||
if expected, err := s.chain.GetHeaders(request1); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
} else if !headersMatch(expected, headers1.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1)
|
||||
t.Fatalf("failed to get expected headers for request 1: %v", err)
|
||||
} else if !headersMatch(expected, resp1.BlockHeadersRequest) {
|
||||
t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp1)
|
||||
}
|
||||
resp2 := resp[3]
|
||||
if expected, err := s.chain.GetHeaders(request2); err != nil {
|
||||
t.Fatalf("failed to get expected block headers: %v", err)
|
||||
} else if !headersMatch(expected, headers2.BlockHeadersRequest) {
|
||||
t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2)
|
||||
t.Fatalf("failed to get expected headers for request 2: %v", err)
|
||||
} else if !headersMatch(expected, resp2.BlockHeadersRequest) {
|
||||
t.Fatalf("headers mismatch: \nexpected %v \ngot %v", expected, resp2)
|
||||
}
|
||||
}
|
||||
|
||||
// collectResponses waits for n messages of type T on the given connection.
|
||||
// The messsages are collected according to the 'identity' function.
|
||||
func collectResponses[T any, P msgTypePtr[T]](conn *Conn, n int, identity func(P) uint64) (map[uint64]P, error) {
|
||||
resp := make(map[uint64]P, n)
|
||||
for range n {
|
||||
r := new(T)
|
||||
if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, r); err != nil {
|
||||
return resp, fmt.Errorf("read error: %v", err)
|
||||
}
|
||||
id := identity(r)
|
||||
if resp[id] != nil {
|
||||
return resp, fmt.Errorf("duplicate response %v", r)
|
||||
}
|
||||
resp[id] = r
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *Suite) TestZeroRequestID(t *utesting.T) {
|
||||
t.Log(`This test sends a GetBlockHeaders message with a request-id of zero,
|
||||
and expects a response.`)
|
||||
|
|
@ -887,7 +904,7 @@ func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Tra
|
|||
from, nonce := s.chain.GetSender(5)
|
||||
for i := 0; i < count; i++ {
|
||||
// Make blob data, max of 2 blobs per tx.
|
||||
blobdata := make([]byte, blobs%3)
|
||||
blobdata := make([]byte, min(blobs, 2))
|
||||
for i := range blobdata {
|
||||
blobdata[i] = discriminator
|
||||
blobs -= 1
|
||||
|
|
|
|||
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
BIN
cmd/devp2p/internal/ethtest/testdata/chain.rlp
vendored
Binary file not shown.
|
|
@ -1,20 +1,27 @@
|
|||
{
|
||||
"HIVE_CANCUN_TIMESTAMP": "840",
|
||||
"HIVE_CANCUN_BLOB_BASE_FEE_UPDATE_FRACTION": "3338477",
|
||||
"HIVE_CANCUN_BLOB_MAX": "6",
|
||||
"HIVE_CANCUN_BLOB_TARGET": "3",
|
||||
"HIVE_CANCUN_TIMESTAMP": "60",
|
||||
"HIVE_CHAIN_ID": "3503995874084926",
|
||||
"HIVE_FORK_ARROW_GLACIER": "60",
|
||||
"HIVE_FORK_BERLIN": "48",
|
||||
"HIVE_FORK_BYZANTIUM": "18",
|
||||
"HIVE_FORK_CONSTANTINOPLE": "24",
|
||||
"HIVE_FORK_GRAY_GLACIER": "66",
|
||||
"HIVE_FORK_ARROW_GLACIER": "0",
|
||||
"HIVE_FORK_BERLIN": "0",
|
||||
"HIVE_FORK_BYZANTIUM": "0",
|
||||
"HIVE_FORK_CONSTANTINOPLE": "0",
|
||||
"HIVE_FORK_GRAY_GLACIER": "0",
|
||||
"HIVE_FORK_HOMESTEAD": "0",
|
||||
"HIVE_FORK_ISTANBUL": "36",
|
||||
"HIVE_FORK_LONDON": "54",
|
||||
"HIVE_FORK_MUIR_GLACIER": "42",
|
||||
"HIVE_FORK_PETERSBURG": "30",
|
||||
"HIVE_FORK_SPURIOUS": "12",
|
||||
"HIVE_FORK_TANGERINE": "6",
|
||||
"HIVE_MERGE_BLOCK_ID": "72",
|
||||
"HIVE_FORK_ISTANBUL": "0",
|
||||
"HIVE_FORK_LONDON": "0",
|
||||
"HIVE_FORK_MUIR_GLACIER": "0",
|
||||
"HIVE_FORK_PETERSBURG": "0",
|
||||
"HIVE_FORK_SPURIOUS": "0",
|
||||
"HIVE_FORK_TANGERINE": "0",
|
||||
"HIVE_MERGE_BLOCK_ID": "0",
|
||||
"HIVE_NETWORK_ID": "3503995874084926",
|
||||
"HIVE_SHANGHAI_TIMESTAMP": "780",
|
||||
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784"
|
||||
"HIVE_PRAGUE_BLOB_BASE_FEE_UPDATE_FRACTION": "5007716",
|
||||
"HIVE_PRAGUE_BLOB_MAX": "9",
|
||||
"HIVE_PRAGUE_BLOB_TARGET": "6",
|
||||
"HIVE_PRAGUE_TIMESTAMP": "120",
|
||||
"HIVE_SHANGHAI_TIMESTAMP": "0",
|
||||
"HIVE_TERMINAL_TOTAL_DIFFICULTY": "131072"
|
||||
}
|
||||
|
|
@ -2,28 +2,35 @@
|
|||
"config": {
|
||||
"chainId": 3503995874084926,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 6,
|
||||
"eip155Block": 12,
|
||||
"eip158Block": 12,
|
||||
"byzantiumBlock": 18,
|
||||
"constantinopleBlock": 24,
|
||||
"petersburgBlock": 30,
|
||||
"istanbulBlock": 36,
|
||||
"muirGlacierBlock": 42,
|
||||
"berlinBlock": 48,
|
||||
"londonBlock": 54,
|
||||
"arrowGlacierBlock": 60,
|
||||
"grayGlacierBlock": 66,
|
||||
"mergeNetsplitBlock": 72,
|
||||
"shanghaiTime": 780,
|
||||
"cancunTime": 840,
|
||||
"terminalTotalDifficulty": 9454784,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0,
|
||||
"byzantiumBlock": 0,
|
||||
"constantinopleBlock": 0,
|
||||
"petersburgBlock": 0,
|
||||
"istanbulBlock": 0,
|
||||
"muirGlacierBlock": 0,
|
||||
"berlinBlock": 0,
|
||||
"londonBlock": 0,
|
||||
"arrowGlacierBlock": 0,
|
||||
"grayGlacierBlock": 0,
|
||||
"mergeNetsplitBlock": 0,
|
||||
"shanghaiTime": 0,
|
||||
"cancunTime": 60,
|
||||
"pragueTime": 120,
|
||||
"terminalTotalDifficulty": 131072,
|
||||
"depositContractAddress": "0x0000000000000000000000000000000000000000",
|
||||
"ethash": {},
|
||||
"blobSchedule": {
|
||||
"cancun": {
|
||||
"target": 3,
|
||||
"max": 6,
|
||||
"baseFeeUpdateFraction": 3338477
|
||||
},
|
||||
"prague": {
|
||||
"target": 6,
|
||||
"max": 9,
|
||||
"baseFeeUpdateFraction": 5007716
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -35,6 +42,18 @@
|
|||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"alloc": {
|
||||
"00000961ef480eb55e80d19ad83579a64c007002": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd",
|
||||
"balance": "0x1"
|
||||
},
|
||||
"0000bbddc7ce488642fb579f8b00f3a590007251": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd",
|
||||
"balance": "0x1"
|
||||
},
|
||||
"0000f90827f1c53a10cb7a02335b175320002935": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500",
|
||||
"balance": "0x1"
|
||||
},
|
||||
"000f3df6d732807ef1319fb7b8bb8522d0beac02": {
|
||||
"code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
|
||||
"balance": "0x2a"
|
||||
|
|
@ -81,6 +100,10 @@
|
|||
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
"7dcd17433742f4c0ca53122ab541d0ba67fc27df": {
|
||||
"code": "0x3680600080376000206000548082558060010160005560005263656d697460206000a2",
|
||||
"balance": "0x0"
|
||||
},
|
||||
"83c7e323d189f18725ac510004fdc2941f8c4a78": {
|
||||
"balance": "0xc097ce7bc90715b34b9f1000000000"
|
||||
},
|
||||
|
|
@ -112,7 +135,7 @@
|
|||
"number": "0x0",
|
||||
"gasUsed": "0x0",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"baseFeePerGas": null,
|
||||
"baseFeePerGas": "0x3b9aca00",
|
||||
"excessBlobGas": null,
|
||||
"blobGasUsed": null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
{
|
||||
"parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39",
|
||||
"parentHash": "0x65151b101682b54cd08ba226f640c14c86176865ff9bfc57e0147dadaeac34bb",
|
||||
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"miner": "0x0000000000000000000000000000000000000000",
|
||||
"stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94",
|
||||
"transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48",
|
||||
"receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
|
||||
"stateRoot": "0xce423ebc60fc7764a43f09f1fe3ae61eef25e3eb8d09b1108f7e7eb77dfff5e6",
|
||||
"transactionsRoot": "0x7ec1ae3989efa75d7bcc766e5e2443afa8a89a5fda42ebba90050e7e702980f7",
|
||||
"receiptsRoot": "0xfe160832b1ca85f38c6674cb0aae3a24693bc49be56e2ecdf3698b71a794de86",
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"difficulty": "0x0",
|
||||
"number": "0x1f4",
|
||||
"gasLimit": "0x47e7c40",
|
||||
"gasUsed": "0x5208",
|
||||
"timestamp": "0x1388",
|
||||
"number": "0x258",
|
||||
"gasLimit": "0x23f3e20",
|
||||
"gasUsed": "0x19d36",
|
||||
"timestamp": "0x1770",
|
||||
"extraData": "0x",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"nonce": "0x0000000000000000",
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"blobGasUsed": "0x0",
|
||||
"excessBlobGas": "0x0",
|
||||
"parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c",
|
||||
"hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||
"parentBeaconBlockRoot": "0xf5003fc8f92358e790a114bce93ce1d9c283c85e1787f8d7d56714d3489b49e6",
|
||||
"requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"hash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0"
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "fcu500",
|
||||
"id": "fcu600",
|
||||
"method": "engine_forkchoiceUpdatedV3",
|
||||
"params": [
|
||||
{
|
||||
"headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||
"safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7",
|
||||
"finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7"
|
||||
"headBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0",
|
||||
"safeBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0",
|
||||
"finalizedBlockHash": "0xce8d86ba17a2ec303155f0e264c58a4b8f94ce3436274cf1924f91acdb7502d0"
|
||||
},
|
||||
null
|
||||
]
|
||||
|
|
|
|||
6492
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
6492
cmd/devp2p/internal/ethtest/testdata/headstate.json
vendored
File diff suppressed because it is too large
Load diff
15735
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
15735
cmd/devp2p/internal/ethtest/testdata/newpayload.json
vendored
File diff suppressed because it is too large
Load diff
5390
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
5390
cmd/devp2p/internal/ethtest/testdata/txinfo.json
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -143,9 +143,6 @@ type testParams struct {
|
|||
|
||||
func cliTestParams(ctx *cli.Context) *testParams {
|
||||
nodeStr := ctx.String(testNodeFlag.Name)
|
||||
if nodeStr == "" {
|
||||
exit(fmt.Errorf("missing -%s", testNodeFlag.Name))
|
||||
}
|
||||
node, err := parseNode(nodeStr)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
|
|
@ -156,14 +153,5 @@ func cliTestParams(ctx *cli.Context) *testParams {
|
|||
jwt: ctx.String(testNodeJWTFlag.Name),
|
||||
chainDir: ctx.String(testChainDirFlag.Name),
|
||||
}
|
||||
if p.engineAPI == "" {
|
||||
exit(fmt.Errorf("missing -%s", testNodeEngineFlag.Name))
|
||||
}
|
||||
if p.jwt == "" {
|
||||
exit(fmt.Errorf("missing -%s", testNodeJWTFlag.Name))
|
||||
}
|
||||
if p.chainDir == "" {
|
||||
exit(fmt.Errorf("missing -%s", testChainDirFlag.Name))
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,26 +39,29 @@ var (
|
|||
}
|
||||
|
||||
// for eth/snap tests
|
||||
testChainDirFlag = &cli.StringFlag{
|
||||
testChainDirFlag = &cli.PathFlag{
|
||||
Name: "chain",
|
||||
Usage: "Test chain directory (required)",
|
||||
Category: flags.TestingCategory,
|
||||
Required: true,
|
||||
}
|
||||
testNodeFlag = &cli.StringFlag{
|
||||
Name: "node",
|
||||
Usage: "Peer-to-Peer endpoint (ENR) of the test node (required)",
|
||||
Category: flags.TestingCategory,
|
||||
Required: true,
|
||||
}
|
||||
testNodeJWTFlag = &cli.StringFlag{
|
||||
Name: "jwtsecret",
|
||||
Usage: "JWT secret for the engine API of the test node (required)",
|
||||
Category: flags.TestingCategory,
|
||||
Value: "0x7365637265747365637265747365637265747365637265747365637265747365",
|
||||
Required: true,
|
||||
}
|
||||
testNodeEngineFlag = &cli.StringFlag{
|
||||
Name: "engineapi",
|
||||
Usage: "Engine API endpoint of the test node (required)",
|
||||
Category: flags.TestingCategory,
|
||||
Required: true,
|
||||
}
|
||||
|
||||
// These two are specific to the discovery tests.
|
||||
|
|
|
|||
|
|
@ -274,10 +274,10 @@ func checkAccumulator(e *era.Era) error {
|
|||
for it.Next() {
|
||||
// 1) next() walks the block index, so we're able to implicitly verify it.
|
||||
if it.Error() != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
|
||||
}
|
||||
block, receipts, err := it.BlockAndReceipts()
|
||||
if it.Error() != nil {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), err)
|
||||
}
|
||||
// 2) recompute tx root and verify against header.
|
||||
|
|
@ -294,6 +294,9 @@ func checkAccumulator(e *era.Era) error {
|
|||
td.Add(td, block.Difficulty())
|
||||
tds = append(tds, new(big.Int).Set(td))
|
||||
}
|
||||
if it.Error() != nil {
|
||||
return fmt.Errorf("error reading block %d: %w", it.Number(), it.Error())
|
||||
}
|
||||
// 4+5) Verify accumulator and total difficulty.
|
||||
got, err := era.ComputeAccumulator(hashes, tds)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
Time: pre.Env.ParentTimestamp,
|
||||
ExcessBlobGas: pre.Env.ParentExcessBlobGas,
|
||||
BlobGasUsed: pre.Env.ParentBlobGasUsed,
|
||||
BaseFee: pre.Env.ParentBaseFee,
|
||||
}
|
||||
header := &types.Header{
|
||||
Time: pre.Env.Timestamp,
|
||||
|
|
@ -373,7 +374,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
|
||||
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
|
||||
sdb := state.NewDatabase(tdb, nil)
|
||||
statedb, _ := state.New(types.EmptyRootHash, sdb)
|
||||
statedb, err := state.New(types.EmptyRootHash, sdb)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create initial state: %v", err))
|
||||
}
|
||||
for addr, a := range accounts {
|
||||
statedb.SetCode(addr, a.Code, tracing.CodeChangeUnspecified)
|
||||
statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis)
|
||||
|
|
@ -383,8 +387,14 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
|
|||
}
|
||||
}
|
||||
// Commit and re-open to start with a clean state.
|
||||
root, _ := statedb.Commit(0, false, false)
|
||||
statedb, _ = state.New(root, sdb)
|
||||
root, err := statedb.Commit(0, false, false)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to commit initial state: %v", err))
|
||||
}
|
||||
statedb, err = state.New(root, sdb)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to reopen state after commit: %v", err))
|
||||
}
|
||||
return statedb
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -296,6 +296,14 @@ func TestT8n(t *testing.T) {
|
|||
output: t8nOutput{alloc: true, result: true},
|
||||
expOut: "exp.json",
|
||||
},
|
||||
{ // Osaka test, EIP-7918 blob gas with parent base fee
|
||||
base: "./testdata/34",
|
||||
input: t8nInput{
|
||||
"alloc.json", "txs.json", "env.json", "Osaka", "",
|
||||
},
|
||||
output: t8nOutput{alloc: true, result: true},
|
||||
expOut: "exp.json",
|
||||
},
|
||||
} {
|
||||
args := []string{"t8n"}
|
||||
args = append(args, tc.output.get()...)
|
||||
|
|
|
|||
6
cmd/evm/testdata/34/README.md
vendored
Normal file
6
cmd/evm/testdata/34/README.md
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
This test verifies that Osaka fork blob gas calculation works correctly when
|
||||
parentBaseFee is provided. It tests the EIP-7918 reserve price calculation
|
||||
which requires parent.BaseFee to be properly set.
|
||||
|
||||
Regression test for: nil pointer dereference when parent.BaseFee was not
|
||||
included in the parent header during Osaka fork blob gas calculations.
|
||||
6
cmd/evm/testdata/34/alloc.json
vendored
Normal file
6
cmd/evm/testdata/34/alloc.json
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||
"balance": "0x1000000000000000000",
|
||||
"nonce": "0x0"
|
||||
}
|
||||
}
|
||||
18
cmd/evm/testdata/34/env.json
vendored
Normal file
18
cmd/evm/testdata/34/env.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"currentCoinbase": "0x0000000000000000000000000000000000000000",
|
||||
"currentDifficulty": "0x0",
|
||||
"currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"currentGasLimit": "0x5f5e100",
|
||||
"currentNumber": "0x1",
|
||||
"currentTimestamp": "0x1000",
|
||||
"parentTimestamp": "0x0",
|
||||
"currentBaseFee": "0x10",
|
||||
"parentBaseFee": "0x0a",
|
||||
"parentGasUsed": "0x0",
|
||||
"parentGasLimit": "0x5f5e100",
|
||||
"currentExcessBlobGas": "0x0",
|
||||
"parentExcessBlobGas": "0x0",
|
||||
"parentBlobGasUsed": "0x20000",
|
||||
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"withdrawals": []
|
||||
}
|
||||
23
cmd/evm/testdata/34/exp.json
vendored
Normal file
23
cmd/evm/testdata/34/exp.json
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"alloc": {
|
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
|
||||
"balance": "0x1000000000000000000"
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"stateRoot": "0x01c28492482a1a1f66224726ef1059a7036fce69d1d2c991b65cd013725d5742",
|
||||
"txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"currentDifficulty": null,
|
||||
"receipts": [],
|
||||
"gasUsed": "0x0",
|
||||
"currentBaseFee": "0x10",
|
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
"currentExcessBlobGas": "0x0",
|
||||
"blobGasUsed": "0x0",
|
||||
"requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"requests": []
|
||||
}
|
||||
}
|
||||
1
cmd/evm/testdata/34/txs.json
vendored
Normal file
1
cmd/evm/testdata/34/txs.json
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
[]
|
||||
|
|
@ -59,6 +59,8 @@ var (
|
|||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.CachePreimagesFlag,
|
||||
utils.OverrideOsaka,
|
||||
utils.OverrideBPO1,
|
||||
utils.OverrideBPO2,
|
||||
utils.OverrideVerkle,
|
||||
}, utils.DatabaseFlags),
|
||||
Description: `
|
||||
|
|
@ -274,6 +276,14 @@ func initGenesis(ctx *cli.Context) error {
|
|||
v := ctx.Uint64(utils.OverrideOsaka.Name)
|
||||
overrides.OverrideOsaka = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideBPO1.Name) {
|
||||
v := ctx.Uint64(utils.OverrideBPO1.Name)
|
||||
overrides.OverrideBPO1 = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideBPO2.Name) {
|
||||
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
||||
overrides.OverrideBPO2 = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||
overrides.OverrideVerkle = &v
|
||||
|
|
|
|||
|
|
@ -227,6 +227,14 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
|||
v := ctx.Uint64(utils.OverrideOsaka.Name)
|
||||
cfg.Eth.OverrideOsaka = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideBPO1.Name) {
|
||||
v := ctx.Uint64(utils.OverrideBPO1.Name)
|
||||
cfg.Eth.OverrideBPO1 = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideBPO2.Name) {
|
||||
v := ctx.Uint64(utils.OverrideBPO2.Name)
|
||||
cfg.Eth.OverrideBPO2 = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideVerkle.Name) {
|
||||
v := ctx.Uint64(utils.OverrideVerkle.Name)
|
||||
cfg.Eth.OverrideVerkle = &v
|
||||
|
|
|
|||
|
|
@ -63,7 +63,10 @@ var (
|
|||
utils.USBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.OverrideOsaka,
|
||||
utils.OverrideBPO1,
|
||||
utils.OverrideBPO2,
|
||||
utils.OverrideVerkle,
|
||||
utils.OverrideGenesisFlag,
|
||||
utils.EnablePersonal, // deprecated
|
||||
utils.TxPoolLocalsFlag,
|
||||
utils.TxPoolNoLocalsFlag,
|
||||
|
|
@ -182,9 +185,12 @@ var (
|
|||
utils.RPCGlobalGasCapFlag,
|
||||
utils.RPCGlobalEVMTimeoutFlag,
|
||||
utils.RPCGlobalTxFeeCapFlag,
|
||||
utils.RPCGlobalLogQueryLimit,
|
||||
utils.AllowUnprotectedTxs,
|
||||
utils.BatchRequestLimit,
|
||||
utils.BatchResponseMaxSize,
|
||||
utils.RPCTxSyncDefaultTimeoutFlag,
|
||||
utils.RPCTxSyncMaxTimeoutFlag,
|
||||
}
|
||||
|
||||
metricsFlags = []cli.Flag{
|
||||
|
|
|
|||
|
|
@ -561,17 +561,11 @@ func dumpState(ctx *cli.Context) error {
|
|||
triedb := utils.MakeTrieDatabase(ctx, stack, db, false, true, false)
|
||||
defer triedb.Close()
|
||||
|
||||
snapConfig := snapshot.Config{
|
||||
CacheSize: 256,
|
||||
Recovery: false,
|
||||
NoBuild: true,
|
||||
AsyncBuild: false,
|
||||
}
|
||||
snaptree, err := snapshot.New(snapConfig, db, triedb, root)
|
||||
stateIt, err := utils.NewStateIterator(triedb, db, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start))
|
||||
accIt, err := stateIt.AccountIterator(root, common.BytesToHash(conf.Start))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -605,7 +599,7 @@ func dumpState(ctx *cli.Context) error {
|
|||
if !conf.SkipStorage {
|
||||
da.Storage = make(map[common.Hash]string)
|
||||
|
||||
stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{})
|
||||
stIt, err := stateIt.StorageIterator(root, accIt.Hash(), common.Hash{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -658,17 +652,11 @@ func snapshotExportPreimages(ctx *cli.Context) error {
|
|||
}
|
||||
root = headBlock.Root()
|
||||
}
|
||||
snapConfig := snapshot.Config{
|
||||
CacheSize: 256,
|
||||
Recovery: false,
|
||||
NoBuild: true,
|
||||
AsyncBuild: false,
|
||||
}
|
||||
snaptree, err := snapshot.New(snapConfig, chaindb, triedb, root)
|
||||
stateIt, err := utils.NewStateIterator(triedb, chaindb, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.ExportSnapshotPreimages(chaindb, snaptree, ctx.Args().First(), root)
|
||||
return utils.ExportSnapshotPreimages(chaindb, stateIt, ctx.Args().First(), root)
|
||||
}
|
||||
|
||||
// checkAccount iterates the snap data layers, and looks up the given account
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ func expandVerkle(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
for i, key := range keylist {
|
||||
log.Info("Reading key", "index", i, "key", keylist[0])
|
||||
log.Info("Reading key", "index", i, "key", key)
|
||||
root.Get(key, chaindb.Get)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
zkruntime "github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime"
|
||||
zkruntime "github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime"
|
||||
)
|
||||
|
||||
// getInput reads the input payload from the zkVM runtime environment.
|
||||
|
|
|
|||
|
|
@ -3,27 +3,28 @@ module github.com/ethereum/go-ethereum/cmd/keeper
|
|||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6
|
||||
github.com/ethereum/go-ethereum v0.0.0-00010101000000-000000000000
|
||||
github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime v0.0.0-20250915074013-fbc07aa2c6f5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.20.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/consensys/gnark-crypto v0.18.0 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.3.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/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/emicklei/dot v1.6.2 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.3 // 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/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
|
||||
github.com/holiman/uint256 v1.3.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
|
|
@ -33,16 +34,13 @@ require (
|
|||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
|
||||
github.com/supranational/blst v0.3.14 // indirect
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/ethereum/go-ethereum => ../../
|
||||
github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime => github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5
|
||||
)
|
||||
replace github.com/ethereum/go-ethereum => ../../
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
|
||||
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
|
|
@ -27,11 +28,10 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK
|
|||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0=
|
||||
github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
|
||||
github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI=
|
||||
github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
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-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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||
|
|
@ -42,8 +42,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
|
||||
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.3 h1:DQ21UU0VSsuGy8+pcMJHDS0CV1bKmJmxsJYK8l3MiLU=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.3/go.mod h1:fyNcYI/yAuLWJxf4uzVtS8VDKeoAaRM8G/+ADz/pRdA=
|
||||
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-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=
|
||||
|
|
@ -59,9 +61,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||
|
|
@ -110,18 +111,14 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
|
|||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo=
|
||||
github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5 h1:MxKlbmI7Dta6O6Nsc9OAer/rOltjoL11CVLMqCiYnxU=
|
||||
github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5/go.mod h1:zk/SUgiiVz2U1ufZ+yM2MHPbD93W25KH5zK3qAxXbT4=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
|
|
@ -132,9 +129,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
|
@ -48,6 +49,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -310,7 +312,7 @@ func ImportHistory(chain *core.BlockChain, dir string, network string) error {
|
|||
return fmt.Errorf("error reading receipts %d: %w", it.Number(), err)
|
||||
}
|
||||
encReceipts := types.EncodeBlockReceiptLists([]types.Receipts{receipts})
|
||||
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, 2^64-1); err != nil {
|
||||
if _, err := chain.InsertReceiptChain([]*types.Block{block}, encReceipts, math.MaxUint64); err != nil {
|
||||
return fmt.Errorf("error inserting body %d: %w", it.Number(), err)
|
||||
}
|
||||
imported += 1
|
||||
|
|
@ -567,9 +569,64 @@ func ExportPreimages(db ethdb.Database, fn string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// StateIterator is a temporary structure for traversing state in order. It serves
|
||||
// as an aggregator for both path scheme and hash scheme implementations and should
|
||||
// be removed once the hash scheme is fully deprecated.
|
||||
type StateIterator struct {
|
||||
scheme string
|
||||
root common.Hash
|
||||
triedb *triedb.Database
|
||||
snapshots *snapshot.Tree
|
||||
}
|
||||
|
||||
// NewStateIterator constructs the state iterator with the specific root.
|
||||
func NewStateIterator(triedb *triedb.Database, db ethdb.Database, root common.Hash) (*StateIterator, error) {
|
||||
if triedb.Scheme() == rawdb.PathScheme {
|
||||
return &StateIterator{
|
||||
scheme: rawdb.PathScheme,
|
||||
root: root,
|
||||
triedb: triedb,
|
||||
}, nil
|
||||
}
|
||||
config := snapshot.Config{
|
||||
CacheSize: 256,
|
||||
Recovery: false,
|
||||
NoBuild: true,
|
||||
AsyncBuild: false,
|
||||
}
|
||||
snapshots, err := snapshot.New(config, db, triedb, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StateIterator{
|
||||
scheme: rawdb.HashScheme,
|
||||
root: root,
|
||||
triedb: triedb,
|
||||
snapshots: snapshots,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AccountIterator creates a new account iterator for the specified root hash and
|
||||
// seeks to a starting account hash.
|
||||
func (it *StateIterator) AccountIterator(root common.Hash, start common.Hash) (snapshot.AccountIterator, error) {
|
||||
if it.scheme == rawdb.PathScheme {
|
||||
return it.triedb.AccountIterator(root, start)
|
||||
}
|
||||
return it.snapshots.AccountIterator(root, start)
|
||||
}
|
||||
|
||||
// StorageIterator creates a new storage iterator for the specified root hash and
|
||||
// account. The iterator will be moved to the specific start position.
|
||||
func (it *StateIterator) StorageIterator(root common.Hash, accountHash common.Hash, start common.Hash) (snapshot.StorageIterator, error) {
|
||||
if it.scheme == rawdb.PathScheme {
|
||||
return it.triedb.StorageIterator(root, accountHash, start)
|
||||
}
|
||||
return it.snapshots.StorageIterator(root, accountHash, start)
|
||||
}
|
||||
|
||||
// ExportSnapshotPreimages exports the preimages corresponding to the enumeration of
|
||||
// the snapshot for a given root.
|
||||
func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn string, root common.Hash) error {
|
||||
func ExportSnapshotPreimages(chaindb ethdb.Database, stateIt *StateIterator, fn string, root common.Hash) error {
|
||||
log.Info("Exporting preimages", "file", fn)
|
||||
|
||||
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
|
||||
|
|
@ -602,7 +659,7 @@ func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn
|
|||
)
|
||||
go func() {
|
||||
defer close(hashCh)
|
||||
accIt, err := snaptree.AccountIterator(root, common.Hash{})
|
||||
accIt, err := stateIt.AccountIterator(root, common.Hash{})
|
||||
if err != nil {
|
||||
log.Error("Failed to create account iterator", "error", err)
|
||||
return
|
||||
|
|
@ -619,7 +676,7 @@ func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn
|
|||
hashCh <- hashAndPreimageSize{Hash: accIt.Hash(), Size: common.AddressLength}
|
||||
|
||||
if acc.Root != (common.Hash{}) && acc.Root != types.EmptyRootHash {
|
||||
stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{})
|
||||
stIt, err := stateIt.StorageIterator(root, accIt.Hash(), common.Hash{})
|
||||
if err != nil {
|
||||
log.Error("Failed to create storage iterator", "error", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -20,11 +20,9 @@ package utils
|
|||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
|
|
@ -248,11 +246,26 @@ var (
|
|||
Usage: "Manually specify the Osaka fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideBPO1 = &cli.Uint64Flag{
|
||||
Name: "override.bpo1",
|
||||
Usage: "Manually specify the bpo1 fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideBPO2 = &cli.Uint64Flag{
|
||||
Name: "override.bpo2",
|
||||
Usage: "Manually specify the bpo2 fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideVerkle = &cli.Uint64Flag{
|
||||
Name: "override.verkle",
|
||||
Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideGenesisFlag = &cli.StringFlag{
|
||||
Name: "override.genesis",
|
||||
Usage: "Load genesis block and configuration from file at this path",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
SyncModeFlag = &cli.StringFlag{
|
||||
Name: "syncmode",
|
||||
Usage: `Blockchain sync mode ("snap" or "full")`,
|
||||
|
|
@ -600,6 +613,24 @@ var (
|
|||
Value: ethconfig.Defaults.RPCTxFeeCap,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
RPCGlobalLogQueryLimit = &cli.IntFlag{
|
||||
Name: "rpc.logquerylimit",
|
||||
Usage: "Maximum number of alternative addresses or topics allowed per search position in eth_getLogs filter criteria (0 = no cap)",
|
||||
Value: ethconfig.Defaults.LogQueryLimit,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
RPCTxSyncDefaultTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "rpc.txsync.defaulttimeout",
|
||||
Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)",
|
||||
Value: ethconfig.Defaults.TxSyncDefaultTimeout,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
RPCTxSyncMaxTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "rpc.txsync.maxtimeout",
|
||||
Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)",
|
||||
Value: ethconfig.Defaults.TxSyncMaxTimeout,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
// Authenticated RPC HTTP settings
|
||||
AuthListenFlag = &cli.StringFlag{
|
||||
Name: "authrpc.addr",
|
||||
|
|
@ -1309,15 +1340,10 @@ func setEtherbase(ctx *cli.Context, cfg *ethconfig.Config) {
|
|||
return
|
||||
}
|
||||
addr := ctx.String(MinerPendingFeeRecipientFlag.Name)
|
||||
if strings.HasPrefix(addr, "0x") || strings.HasPrefix(addr, "0X") {
|
||||
addr = addr[2:]
|
||||
}
|
||||
b, err := hex.DecodeString(addr)
|
||||
if err != nil || len(b) != common.AddressLength {
|
||||
if !common.IsHexAddress(addr) {
|
||||
Fatalf("-%s: invalid pending block producer address %q", MinerPendingFeeRecipientFlag.Name, addr)
|
||||
return
|
||||
}
|
||||
cfg.Miner.PendingFeeRecipient = common.BytesToAddress(b)
|
||||
cfg.Miner.PendingFeeRecipient = common.HexToAddress(addr)
|
||||
}
|
||||
|
||||
func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||
|
|
@ -1578,7 +1604,7 @@ func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
|
|||
// SetEthConfig applies eth-related command line flags to the config.
|
||||
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
||||
// Avoid conflicting network flags, don't allow network id override on preset networks
|
||||
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, NetworkIdFlag)
|
||||
flags.CheckExclusive(ctx, MainnetFlag, DeveloperFlag, SepoliaFlag, HoleskyFlag, HoodiFlag, NetworkIdFlag, OverrideGenesisFlag)
|
||||
flags.CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer
|
||||
|
||||
// Set configurations from CLI flags
|
||||
|
|
@ -1604,7 +1630,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
}
|
||||
// Ensure Go's GC ignores the database cache for trigger percentage
|
||||
cache := ctx.Int(CacheFlag.Name)
|
||||
gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))
|
||||
gogc := max(20, min(100, 100/(float64(cache)/1024)))
|
||||
|
||||
log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
|
||||
godebug.SetGCPercent(int(gogc))
|
||||
|
|
@ -1699,6 +1725,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(CacheLogSizeFlag.Name) {
|
||||
cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
|
||||
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
|
||||
}
|
||||
if ctx.IsSet(RPCTxSyncDefaultTimeoutFlag.Name) {
|
||||
cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultTimeoutFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
|
||||
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
|
||||
}
|
||||
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
|
||||
// If snap-sync is requested, this flag is also required
|
||||
if cfg.SyncMode == ethconfig.SnapSync {
|
||||
|
|
@ -1855,6 +1890,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if !ctx.IsSet(MinerGasPriceFlag.Name) {
|
||||
cfg.Miner.GasPrice = big.NewInt(1)
|
||||
}
|
||||
case ctx.String(OverrideGenesisFlag.Name) != "":
|
||||
f, err := os.Open(ctx.String(OverrideGenesisFlag.Name))
|
||||
if err != nil {
|
||||
Fatalf("Failed to read genesis file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
genesis := new(core.Genesis)
|
||||
if err := json.NewDecoder(f).Decode(genesis); err != nil {
|
||||
Fatalf("Invalid genesis file: %v", err)
|
||||
}
|
||||
cfg.Genesis = genesis
|
||||
default:
|
||||
if cfg.NetworkId == 1 {
|
||||
SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash)
|
||||
|
|
@ -1922,11 +1969,15 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig {
|
|||
} else {
|
||||
Fatalf("Could not parse --%s: %v", BeaconGenesisRootFlag.Name, err)
|
||||
}
|
||||
configFile := ctx.String(BeaconConfigFlag.Name)
|
||||
if err := config.ChainConfig.LoadForks(configFile); err != nil {
|
||||
Fatalf("Could not load beacon chain config '%s': %v", configFile, err)
|
||||
configPath := ctx.String(BeaconConfigFlag.Name)
|
||||
file, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
Fatalf("failed to read beacon chain config file '%s': %v", configPath, err)
|
||||
}
|
||||
log.Info("Using custom beacon chain config", "file", configFile)
|
||||
if err := config.ChainConfig.LoadForks(file); err != nil {
|
||||
Fatalf("Could not load beacon chain config '%s': %v", configPath, err)
|
||||
}
|
||||
log.Info("Using custom beacon chain config", "file", configPath)
|
||||
} else {
|
||||
if ctx.IsSet(BeaconGenesisRootFlag.Name) {
|
||||
Fatalf("Genesis root is specified but custom beacon chain config is missing")
|
||||
|
|
@ -2017,7 +2068,8 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst
|
|||
// RegisterFilterAPI adds the eth log filtering RPC API to the node.
|
||||
func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem {
|
||||
filterSystem := filters.NewFilterSystem(backend, filters.Config{
|
||||
LogCacheSize: ethcfg.FilterLogCacheSize,
|
||||
LogCacheSize: ethcfg.FilterLogCacheSize,
|
||||
LogQueryLimit: ethcfg.LogQueryLimit,
|
||||
})
|
||||
stack.RegisterAPIs([]rpc.API{{
|
||||
Namespace: "eth",
|
||||
|
|
|
|||
|
|
@ -182,13 +182,14 @@ func (s *filterTestSuite) loadQueries() error {
|
|||
|
||||
// filterQuery is a single query for testing.
|
||||
type filterQuery struct {
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
Address []common.Address `json:"address"`
|
||||
Topics [][]common.Hash `json:"topics"`
|
||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||
results []types.Log
|
||||
Err error `json:"error,omitempty"`
|
||||
FromBlock int64 `json:"fromBlock"`
|
||||
ToBlock int64 `json:"toBlock"`
|
||||
lastBlockHash common.Hash
|
||||
Address []common.Address `json:"address"`
|
||||
Topics [][]common.Hash `json:"topics"`
|
||||
ResultHash *common.Hash `json:"resultHash,omitempty"`
|
||||
results []types.Log
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (fq *filterQuery) isWildcard() bool {
|
||||
|
|
|
|||
337
cmd/workload/filtertestfuzz.go
Normal file
337
cmd/workload/filtertestfuzz.go
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const maxFilterRangeForTestFuzz = 300
|
||||
|
||||
var (
|
||||
filterFuzzCommand = &cli.Command{
|
||||
Name: "filterfuzz",
|
||||
Usage: "Generates queries and compares results against matches derived from receipts",
|
||||
ArgsUsage: "<RPC endpoint URL>",
|
||||
Action: filterFuzzCmd,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
)
|
||||
|
||||
// filterFuzzCmd is the main function of the filter fuzzer.
|
||||
func filterFuzzCmd(ctx *cli.Context) error {
|
||||
f := newFilterTestGen(ctx, maxFilterRangeForTestFuzz)
|
||||
var lastHead *types.Header
|
||||
headerCache := lru.NewCache[common.Hash, *types.Header](200)
|
||||
|
||||
commonAncestor := func(oldPtr, newPtr *types.Header) *types.Header {
|
||||
if oldPtr == nil || newPtr == nil {
|
||||
return nil
|
||||
}
|
||||
if newPtr.Number.Uint64() > oldPtr.Number.Uint64()+100 || oldPtr.Number.Uint64() > newPtr.Number.Uint64()+100 {
|
||||
return nil
|
||||
}
|
||||
for oldPtr.Hash() != newPtr.Hash() {
|
||||
if newPtr.Number.Uint64() >= oldPtr.Number.Uint64() {
|
||||
if parent, _ := headerCache.Get(newPtr.ParentHash); parent != nil {
|
||||
newPtr = parent
|
||||
} else {
|
||||
newPtr, _ = getHeaderByHash(f.client, newPtr.ParentHash)
|
||||
if newPtr == nil {
|
||||
return nil
|
||||
}
|
||||
headerCache.Add(newPtr.Hash(), newPtr)
|
||||
}
|
||||
}
|
||||
if oldPtr.Number.Uint64() > newPtr.Number.Uint64() {
|
||||
oldPtr, _ = headerCache.Get(oldPtr.ParentHash)
|
||||
if oldPtr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return newPtr
|
||||
}
|
||||
|
||||
fetchHead := func() (*types.Header, bool) {
|
||||
currentHead, err := getLatestHeader(f.client)
|
||||
if err != nil {
|
||||
fmt.Println("Could not fetch head block", err)
|
||||
return nil, false
|
||||
}
|
||||
headerCache.Add(currentHead.Hash(), currentHead)
|
||||
if lastHead != nil && currentHead.Hash() == lastHead.Hash() {
|
||||
return currentHead, false
|
||||
}
|
||||
f.blockLimit = currentHead.Number.Int64()
|
||||
ca := commonAncestor(lastHead, currentHead)
|
||||
fmt.Print("*** New head ", f.blockLimit)
|
||||
if ca == nil {
|
||||
fmt.Println(" <no common ancestor>")
|
||||
} else {
|
||||
if reorged := lastHead.Number.Uint64() - ca.Number.Uint64(); reorged > 0 {
|
||||
fmt.Print(" reorged ", reorged)
|
||||
}
|
||||
if missed := currentHead.Number.Uint64() - ca.Number.Uint64() - 1; missed > 0 {
|
||||
fmt.Print(" missed ", missed)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
lastHead = currentHead
|
||||
return currentHead, true
|
||||
}
|
||||
|
||||
tryExtendQuery := func(query *filterQuery) *filterQuery {
|
||||
for {
|
||||
extQuery := f.extendRange(query)
|
||||
if extQuery == nil {
|
||||
return query
|
||||
}
|
||||
extQuery.checkLastBlockHash(f.client)
|
||||
extQuery.run(f.client, nil)
|
||||
if extQuery.Err == nil && len(extQuery.results) == 0 {
|
||||
// query is useless now due to major reorg; abandon and continue
|
||||
fmt.Println("Zero length results")
|
||||
return nil
|
||||
}
|
||||
if extQuery.Err != nil {
|
||||
extQuery.printError()
|
||||
return nil
|
||||
}
|
||||
if len(extQuery.results) > maxFilterResultSize {
|
||||
return query
|
||||
}
|
||||
query = extQuery
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
mmQuery *filterQuery
|
||||
mmRetry, mmNextRetry int
|
||||
)
|
||||
|
||||
mainLoop:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
var query *filterQuery
|
||||
if mmQuery != nil {
|
||||
if mmRetry == 0 {
|
||||
query = mmQuery
|
||||
mmRetry = mmNextRetry
|
||||
mmNextRetry *= 2
|
||||
query.checkLastBlockHash(f.client)
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
query.printError()
|
||||
continue
|
||||
}
|
||||
fmt.Println("Retrying query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
|
||||
} else {
|
||||
mmRetry--
|
||||
}
|
||||
}
|
||||
if query == nil {
|
||||
currentHead, isNewHead := fetchHead()
|
||||
if currentHead == nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
continue mainLoop
|
||||
}
|
||||
if isNewHead {
|
||||
query = f.newHeadSeedQuery(currentHead.Number.Int64())
|
||||
} else {
|
||||
query = f.newQuery()
|
||||
}
|
||||
query.checkLastBlockHash(f.client)
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
query.printError()
|
||||
continue
|
||||
}
|
||||
fmt.Println("New query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
|
||||
if len(query.results) == 0 || len(query.results) > maxFilterResultSize {
|
||||
continue mainLoop
|
||||
}
|
||||
if query = tryExtendQuery(query); query == nil {
|
||||
continue mainLoop
|
||||
}
|
||||
}
|
||||
if !query.checkLastBlockHash(f.client) {
|
||||
fmt.Println("Reorg during search")
|
||||
continue mainLoop
|
||||
}
|
||||
// now we have a new query; check results
|
||||
results, err := query.getResultsFromReceipts(f.client)
|
||||
if err != nil {
|
||||
fmt.Println("Could not fetch results from receipts", err)
|
||||
continue mainLoop
|
||||
}
|
||||
if !query.checkLastBlockHash(f.client) {
|
||||
fmt.Println("Reorg during search")
|
||||
continue mainLoop
|
||||
}
|
||||
if !reflect.DeepEqual(query.results, results) {
|
||||
fmt.Println("Results mismatch from:", query.FromBlock, "to:", query.ToBlock, "addresses:", query.Address, "topics:", query.Topics)
|
||||
resShared, resGetLogs, resReceipts := compareResults(query.results, results)
|
||||
fmt.Println(" shared:", len(resShared))
|
||||
fmt.Println(" only from getLogs:", len(resGetLogs), resGetLogs)
|
||||
fmt.Println(" only from receipts:", len(resReceipts), resReceipts)
|
||||
if mmQuery != query {
|
||||
mmQuery = query
|
||||
mmRetry = 0
|
||||
mmNextRetry = 1
|
||||
}
|
||||
continue mainLoop
|
||||
}
|
||||
fmt.Println("Successful query from:", query.FromBlock, "to:", query.ToBlock, "results:", len(query.results))
|
||||
f.storeQuery(query)
|
||||
}
|
||||
}
|
||||
|
||||
func compareResults(a, b []types.Log) (shared, onlya, onlyb []types.Log) {
|
||||
for len(a) > 0 && len(b) > 0 {
|
||||
if reflect.DeepEqual(a[0], b[0]) {
|
||||
shared = append(shared, a[0])
|
||||
a = a[1:]
|
||||
b = b[1:]
|
||||
} else {
|
||||
for i := 1; ; i++ {
|
||||
if i >= len(a) { // b[0] not found in a
|
||||
onlyb = append(onlyb, b[0])
|
||||
b = b[1:]
|
||||
break
|
||||
}
|
||||
if i >= len(b) { // a[0] not found in b
|
||||
onlya = append(onlya, a[0])
|
||||
a = a[1:]
|
||||
break
|
||||
}
|
||||
if reflect.DeepEqual(b[0], a[i]) { // a[:i] not found in b
|
||||
onlya = append(onlya, a[:i]...)
|
||||
a = a[i:]
|
||||
break
|
||||
}
|
||||
if reflect.DeepEqual(a[0], b[i]) { // b[:i] not found in a
|
||||
onlyb = append(onlyb, b[:i]...)
|
||||
b = b[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onlya = append(onlya, a...)
|
||||
onlyb = append(onlyb, b...)
|
||||
return
|
||||
}
|
||||
|
||||
func getLatestHeader(client *client) (*types.Header, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
return client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber)))
|
||||
}
|
||||
|
||||
func getHeaderByHash(client *client, hash common.Hash) (*types.Header, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
return client.Eth.HeaderByHash(ctx, hash)
|
||||
}
|
||||
|
||||
// newHeadSeedQuery creates a query that gets all logs from the latest head.
|
||||
func (s *filterTestGen) newHeadSeedQuery(head int64) *filterQuery {
|
||||
return &filterQuery{
|
||||
FromBlock: head,
|
||||
ToBlock: head,
|
||||
}
|
||||
}
|
||||
|
||||
func (fq *filterQuery) checkLastBlockHash(client *client) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(fq.ToBlock))
|
||||
if err != nil {
|
||||
fmt.Println("Cound not fetch last block hash of query number:", fq.ToBlock, "error:", err)
|
||||
fq.lastBlockHash = common.Hash{}
|
||||
return false
|
||||
}
|
||||
hash := header.Hash()
|
||||
if fq.lastBlockHash == hash {
|
||||
return true
|
||||
}
|
||||
fq.lastBlockHash = hash
|
||||
return false
|
||||
}
|
||||
|
||||
func (fq *filterQuery) filterLog(log *types.Log) bool {
|
||||
if len(fq.Address) > 0 && !slices.Contains(fq.Address, log.Address) {
|
||||
return false
|
||||
}
|
||||
// If the to filtered topics is greater than the amount of topics in logs, skip.
|
||||
if len(fq.Topics) > len(log.Topics) {
|
||||
return false
|
||||
}
|
||||
for i, sub := range fq.Topics {
|
||||
if len(sub) == 0 {
|
||||
continue // empty rule set == wildcard
|
||||
}
|
||||
if !slices.Contains(sub, log.Topics[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (fq *filterQuery) getResultsFromReceipts(client *client) ([]types.Log, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
var results []types.Log
|
||||
for blockNumber := fq.FromBlock; blockNumber <= fq.ToBlock; blockNumber++ {
|
||||
receipts, err := client.Eth.BlockReceipts(ctx, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(blockNumber)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, receipt := range receipts {
|
||||
for _, log := range receipt.Logs {
|
||||
if fq.filterLog(log) {
|
||||
results = append(results, *log)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
|
@ -32,6 +32,17 @@ import (
|
|||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// Parameter of the random filter query generator.
|
||||
maxFilterRangeForTestGen = 100000000000
|
||||
maxFilterResultSize = 1000
|
||||
filterBuckets = 10
|
||||
maxFilterBucketSize = 100
|
||||
filterSeedChance = 10
|
||||
filterMergeChance = 45
|
||||
filterExtendChance = 50
|
||||
)
|
||||
|
||||
var (
|
||||
filterGenerateCommand = &cli.Command{
|
||||
Name: "filtergen",
|
||||
|
|
@ -58,7 +69,7 @@ var (
|
|||
|
||||
// filterGenCmd is the main function of the filter tests generator.
|
||||
func filterGenCmd(ctx *cli.Context) error {
|
||||
f := newFilterTestGen(ctx)
|
||||
f := newFilterTestGen(ctx, maxFilterRangeForTestGen)
|
||||
lastWrite := time.Now()
|
||||
for {
|
||||
select {
|
||||
|
|
@ -67,7 +78,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
default:
|
||||
}
|
||||
|
||||
f.updateFinalizedBlock()
|
||||
f.setLimitToFinalizedBlock()
|
||||
query := f.newQuery()
|
||||
query.run(f.client, nil)
|
||||
if query.Err != nil {
|
||||
|
|
@ -75,7 +86,7 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
exit("filter query failed")
|
||||
}
|
||||
if len(query.results) > 0 && len(query.results) <= maxFilterResultSize {
|
||||
for {
|
||||
for rand.Intn(100) < filterExtendChance {
|
||||
extQuery := f.extendRange(query)
|
||||
if extQuery == nil {
|
||||
break
|
||||
|
|
@ -108,39 +119,32 @@ func filterGenCmd(ctx *cli.Context) error {
|
|||
|
||||
// filterTestGen is the filter query test generator.
|
||||
type filterTestGen struct {
|
||||
client *client
|
||||
queryFile string
|
||||
client *client
|
||||
queryFile string
|
||||
maxFilterRange int64
|
||||
|
||||
finalizedBlock int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
blockLimit int64
|
||||
queries [filterBuckets][]*filterQuery
|
||||
}
|
||||
|
||||
func newFilterTestGen(ctx *cli.Context) *filterTestGen {
|
||||
func newFilterTestGen(ctx *cli.Context, maxFilterRange int64) *filterTestGen {
|
||||
return &filterTestGen{
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
client: makeClient(ctx),
|
||||
queryFile: ctx.String(filterQueryFileFlag.Name),
|
||||
maxFilterRange: maxFilterRange,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *filterTestGen) updateFinalizedBlock() {
|
||||
s.finalizedBlock = mustGetFinalizedBlock(s.client)
|
||||
func (s *filterTestGen) setLimitToFinalizedBlock() {
|
||||
s.blockLimit = mustGetFinalizedBlock(s.client)
|
||||
}
|
||||
|
||||
const (
|
||||
// Parameter of the random filter query generator.
|
||||
maxFilterRange = 10000000
|
||||
maxFilterResultSize = 300
|
||||
filterBuckets = 10
|
||||
maxFilterBucketSize = 100
|
||||
filterSeedChance = 10
|
||||
filterMergeChance = 45
|
||||
)
|
||||
|
||||
// storeQuery adds a filter query to the output file.
|
||||
func (s *filterTestGen) storeQuery(query *filterQuery) {
|
||||
query.ResultHash = new(common.Hash)
|
||||
*query.ResultHash = query.calculateHash()
|
||||
logRatio := math.Log(float64(len(query.results))*float64(s.finalizedBlock)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(s.finalizedBlock)*maxFilterResultSize)
|
||||
maxFilterRange := min(s.maxFilterRange, s.blockLimit)
|
||||
logRatio := math.Log(float64(len(query.results))*float64(maxFilterRange)/float64(query.ToBlock+1-query.FromBlock)) / math.Log(float64(maxFilterRange)*maxFilterResultSize)
|
||||
bucket := int(math.Floor(logRatio * filterBuckets))
|
||||
if bucket >= filterBuckets {
|
||||
bucket = filterBuckets - 1
|
||||
|
|
@ -160,13 +164,13 @@ func (s *filterTestGen) storeQuery(query *filterQuery) {
|
|||
func (s *filterTestGen) extendRange(q *filterQuery) *filterQuery {
|
||||
rangeLen := q.ToBlock + 1 - q.FromBlock
|
||||
extLen := rand.Int63n(rangeLen) + 1
|
||||
if rangeLen+extLen > s.finalizedBlock {
|
||||
if rangeLen+extLen > min(s.maxFilterRange, s.blockLimit) {
|
||||
return nil
|
||||
}
|
||||
extBefore := min(rand.Int63n(extLen+1), q.FromBlock)
|
||||
extAfter := extLen - extBefore
|
||||
if q.ToBlock+extAfter > s.finalizedBlock {
|
||||
d := q.ToBlock + extAfter - s.finalizedBlock
|
||||
if q.ToBlock+extAfter > s.blockLimit {
|
||||
d := q.ToBlock + extAfter - s.blockLimit
|
||||
extAfter -= d
|
||||
if extBefore+d <= q.FromBlock {
|
||||
extBefore += d
|
||||
|
|
@ -203,7 +207,7 @@ func (s *filterTestGen) newQuery() *filterQuery {
|
|||
|
||||
// newSeedQuery creates a query that gets all logs in a random non-finalized block.
|
||||
func (s *filterTestGen) newSeedQuery() *filterQuery {
|
||||
block := rand.Int63n(s.finalizedBlock + 1)
|
||||
block := rand.Int63n(s.blockLimit + 1)
|
||||
return &filterQuery{
|
||||
FromBlock: block,
|
||||
ToBlock: block,
|
||||
|
|
@ -358,6 +362,7 @@ func (s *filterTestGen) writeQueries() {
|
|||
func mustGetFinalizedBlock(client *client) int64 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
header, err := client.Eth.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
|
||||
if err != nil {
|
||||
exit(fmt.Errorf("could not fetch finalized header (error: %v)", err))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func init() {
|
|||
filterGenerateCommand,
|
||||
traceGenerateCommand,
|
||||
filterPerfCommand,
|
||||
filterFuzzCommand,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -408,7 +408,6 @@ func TestUnmarshalFixedUnprefixedText(t *testing.T) {
|
|||
{input: "0x2", wantErr: ErrOddLength},
|
||||
{input: "2", wantErr: ErrOddLength},
|
||||
{input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")},
|
||||
{input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")},
|
||||
// check that output is not modified for partially correct input
|
||||
{input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}},
|
||||
{input: "0x444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}},
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ func TestMustParseUint64(t *testing.T) {
|
|||
func TestMustParseUint64Panic(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Error("MustParseBig should've panicked")
|
||||
t.Error("MustParseUint64 should've panicked")
|
||||
}
|
||||
}()
|
||||
MustParseUint64("ggg")
|
||||
|
|
|
|||
|
|
@ -71,16 +71,6 @@ func New(ethone consensus.Engine) *Beacon {
|
|||
return &Beacon{ethone: ethone}
|
||||
}
|
||||
|
||||
// isPostMerge reports whether the given block number is assumed to be post-merge.
|
||||
// Here we check the MergeNetsplitBlock to allow configuring networks with a PoW or
|
||||
// PoA chain for unit testing purposes.
|
||||
func isPostMerge(config *params.ChainConfig, blockNum uint64, timestamp uint64) bool {
|
||||
mergedAtGenesis := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.Sign() == 0
|
||||
return mergedAtGenesis ||
|
||||
config.MergeNetsplitBlock != nil && blockNum >= config.MergeNetsplitBlock.Uint64() ||
|
||||
config.ShanghaiTime != nil && timestamp >= *config.ShanghaiTime
|
||||
}
|
||||
|
||||
// Author implements consensus.Engine, returning the verified author of the block.
|
||||
func (beacon *Beacon) Author(header *types.Header) (common.Address, error) {
|
||||
if !beacon.IsPoSHeader(header) {
|
||||
|
|
@ -328,7 +318,7 @@ func (beacon *Beacon) verifyHeaders(chain consensus.ChainHeaderReader, headers [
|
|||
// Prepare implements consensus.Engine, initializing the difficulty field of a
|
||||
// header to conform to the beacon protocol. The changes are done inline.
|
||||
func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
if !isPostMerge(chain.Config(), header.Number.Uint64(), header.Time) {
|
||||
if !chain.Config().IsPostMerge(header.Number.Uint64(), header.Time) {
|
||||
return beacon.ethone.Prepare(chain, header)
|
||||
}
|
||||
header.Difficulty = beaconDifficulty
|
||||
|
|
@ -442,7 +432,7 @@ func (beacon *Beacon) SealHash(header *types.Header) common.Hash {
|
|||
// the difficulty that a new block should have when created at time
|
||||
// given the parent block's time and difficulty.
|
||||
func (beacon *Beacon) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
||||
if !isPostMerge(chain.Config(), parent.Number.Uint64()+1, time) {
|
||||
if !chain.Config().IsPostMerge(parent.Number.Uint64()+1, time) {
|
||||
return beacon.ethone.CalcDifficulty(chain, time, parent)
|
||||
}
|
||||
return beaconDifficulty
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uin
|
|||
|
||||
func benchWriteChain(b *testing.B, full bool, count uint64) {
|
||||
genesis := &Genesis{Config: params.AllEthashProtocolChanges}
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false)
|
||||
if err != nil {
|
||||
b.Fatalf("error opening database: %v", err)
|
||||
|
|
@ -326,9 +326,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
|
|||
db.Close()
|
||||
options := DefaultConfig().WithArchive(true)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
pdb, err = pebble.New(dir, 1024, 128, "", false)
|
||||
if err != nil {
|
||||
b.Fatalf("error opening database: %v", err)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
|
@ -395,7 +394,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
|||
bc.statedb = state.NewDatabase(bc.triedb, nil)
|
||||
bc.validator = NewBlockValidator(chainConfig, bc)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(chainConfig, bc.hc)
|
||||
bc.processor = NewStateProcessor(bc.hc)
|
||||
|
||||
genesisHeader := bc.GetHeaderByNumber(0)
|
||||
if genesisHeader == nil {
|
||||
|
|
@ -1691,7 +1690,12 @@ func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types
|
|||
// Set new head.
|
||||
bc.writeHeadBlock(block)
|
||||
|
||||
bc.chainFeed.Send(ChainEvent{Header: block.Header()})
|
||||
bc.chainFeed.Send(ChainEvent{
|
||||
Header: block.Header(),
|
||||
Receipts: receipts,
|
||||
Transactions: block.Transactions(),
|
||||
})
|
||||
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
}
|
||||
|
|
@ -2343,6 +2347,13 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co
|
|||
// collectLogs collects the logs that were generated or removed during the
|
||||
// processing of a block. These logs are later announced as deleted or reborn.
|
||||
func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
|
||||
_, logs := bc.collectReceiptsAndLogs(b, removed)
|
||||
return logs
|
||||
}
|
||||
|
||||
// collectReceiptsAndLogs retrieves receipts from the database and returns both receipts and logs.
|
||||
// This avoids duplicate database reads when both are needed.
|
||||
func (bc *BlockChain) collectReceiptsAndLogs(b *types.Block, removed bool) ([]*types.Receipt, []*types.Log) {
|
||||
var blobGasPrice *big.Int
|
||||
if b.ExcessBlobGas() != nil {
|
||||
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header())
|
||||
|
|
@ -2360,7 +2371,7 @@ func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log {
|
|||
logs = append(logs, log)
|
||||
}
|
||||
}
|
||||
return logs
|
||||
return receipts, logs
|
||||
}
|
||||
|
||||
// reorg takes two blocks, an old chain and a new chain and will reconstruct the
|
||||
|
|
@ -2589,8 +2600,14 @@ func (bc *BlockChain) SetCanonical(head *types.Block) (common.Hash, error) {
|
|||
bc.writeHeadBlock(head)
|
||||
|
||||
// Emit events
|
||||
logs := bc.collectLogs(head, false)
|
||||
bc.chainFeed.Send(ChainEvent{Header: head.Header()})
|
||||
receipts, logs := bc.collectReceiptsAndLogs(head, false)
|
||||
|
||||
bc.chainFeed.Send(ChainEvent{
|
||||
Header: head.Header(),
|
||||
Receipts: receipts,
|
||||
Transactions: head.Transactions(),
|
||||
})
|
||||
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
}
|
||||
|
|
@ -2663,13 +2680,11 @@ func (bc *BlockChain) reportBlock(block *types.Block, res *ProcessResult, err er
|
|||
// logForkReadiness will write a log when a future fork is scheduled, but not
|
||||
// active. This is useful so operators know their client is ready for the fork.
|
||||
func (bc *BlockChain) logForkReadiness(block *types.Block) {
|
||||
config := bc.Config()
|
||||
current, last := config.LatestFork(block.Time()), config.LatestFork(math.MaxUint64)
|
||||
current := bc.Config().LatestFork(block.Time())
|
||||
|
||||
// Short circuit if the timestamp of the last fork is undefined,
|
||||
// or if the network has already passed the last configured fork.
|
||||
t := config.Timestamp(last)
|
||||
if t == nil || current >= last {
|
||||
// Short circuit if the timestamp of the last fork is undefined.
|
||||
t := bc.Config().Timestamp(current + 1)
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
at := time.Unix(int64(*t), 0)
|
||||
|
|
@ -2679,7 +2694,7 @@ func (bc *BlockChain) logForkReadiness(block *types.Block) {
|
|||
// - Enough time has passed since last alert
|
||||
now := time.Now()
|
||||
if now.Before(at) && now.After(bc.lastForkReadyAlert.Add(forkReadyInterval)) {
|
||||
log.Info("Ready for fork activation", "fork", last, "date", at.Format(time.RFC822),
|
||||
log.Info("Ready for fork activation", "fork", current+1, "date", at.Format(time.RFC822),
|
||||
"remaining", time.Until(at).Round(time.Second), "timestamp", at.Unix())
|
||||
bc.lastForkReadyAlert = time.Now()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ type NewTxsEvent struct{ Txs []*types.Transaction }
|
|||
type RemovedLogsEvent struct{ Logs []*types.Log }
|
||||
|
||||
type ChainEvent struct {
|
||||
Header *types.Header
|
||||
Header *types.Header
|
||||
Receipts []*types.Receipt
|
||||
Transactions []*types.Transaction
|
||||
}
|
||||
|
||||
type ChainHeadEvent struct {
|
||||
|
|
|
|||
|
|
@ -25,21 +25,16 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// ChainContext supports retrieving headers and consensus parameters from the
|
||||
// current blockchain to be used during transaction processing.
|
||||
type ChainContext interface {
|
||||
consensus.ChainHeaderReader
|
||||
|
||||
// Engine retrieves the chain's consensus engine.
|
||||
Engine() consensus.Engine
|
||||
|
||||
// GetHeader returns the header corresponding to the hash/number argument pair.
|
||||
GetHeader(common.Hash, uint64) *types.Header
|
||||
|
||||
// Config returns the chain's configuration.
|
||||
Config() *params.ChainConfig
|
||||
}
|
||||
|
||||
// NewEVMBlockContext creates a new context for use in the EVM.
|
||||
|
|
|
|||
|
|
@ -95,8 +95,14 @@ func TestCreation(t *testing.T) {
|
|||
{1735372, 1706655071, ID{Hash: checksumToBytes(0xf7f9bc08), Next: 1706655072}}, // Last Shanghai block
|
||||
{1735372, 1706655072, ID{Hash: checksumToBytes(0x88cf81d9), Next: 1741159776}}, // First Cancun block
|
||||
{1735372, 1741159775, ID{Hash: checksumToBytes(0x88cf81d9), Next: 1741159776}}, // Last Cancun block
|
||||
{1735372, 1741159776, ID{Hash: checksumToBytes(0xed88b5fd), Next: 0}}, // First Prague block
|
||||
{1735372, 2741159776, ID{Hash: checksumToBytes(0xed88b5fd), Next: 0}}, // Future Prague block
|
||||
{1735372, 1741159776, ID{Hash: checksumToBytes(0xed88b5fd), Next: 1760427360}}, // First Prague block
|
||||
{1735372, 1760427359, ID{Hash: checksumToBytes(0xed88b5fd), Next: 1760427360}}, // Last Prague block
|
||||
{1735372, 1760427360, ID{Hash: checksumToBytes(0xe2ae4999), Next: 1761017184}}, // First Osaka block
|
||||
{1735372, 1761017183, ID{Hash: checksumToBytes(0xe2ae4999), Next: 1761017184}}, // Last Osaka block
|
||||
{1735372, 1761017184, ID{Hash: checksumToBytes(0x56078a1e), Next: 1761607008}}, // First BPO1 block
|
||||
{1735372, 1761607007, ID{Hash: checksumToBytes(0x56078a1e), Next: 1761607008}}, // Last BPO1 block
|
||||
{1735372, 1761607008, ID{Hash: checksumToBytes(0x268956b6), Next: 0}}, // First BPO2 block
|
||||
{1735372, 2000000000, ID{Hash: checksumToBytes(0x268956b6), Next: 0}}, // Future BPO2 block
|
||||
},
|
||||
},
|
||||
// Holesky test cases
|
||||
|
|
@ -110,8 +116,14 @@ func TestCreation(t *testing.T) {
|
|||
{123, 1707305663, ID{Hash: checksumToBytes(0xfd4f016b), Next: 1707305664}}, // Last Shanghai block
|
||||
{123, 1707305664, ID{Hash: checksumToBytes(0x9b192ad0), Next: 1740434112}}, // First Cancun block
|
||||
{123, 1740434111, ID{Hash: checksumToBytes(0x9b192ad0), Next: 1740434112}}, // Last Cancun block
|
||||
{123, 1740434112, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 0}}, // First Prague block
|
||||
{123, 2740434112, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 0}}, // Future Prague block
|
||||
{123, 1740434112, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 1759308480}}, // First Prague block
|
||||
{123, 1759308479, ID{Hash: checksumToBytes(0xdfbd9bed), Next: 1759308480}}, // Last Prague block
|
||||
{123, 1759308480, ID{Hash: checksumToBytes(0x783def52), Next: 1759800000}}, // First Osaka block
|
||||
{123, 1759799999, ID{Hash: checksumToBytes(0x783def52), Next: 1759800000}}, // Last Osaka block
|
||||
{123, 1759800000, ID{Hash: checksumToBytes(0xa280a45c), Next: 1760389824}}, // First BPO1 block
|
||||
{123, 1760389823, ID{Hash: checksumToBytes(0xa280a45c), Next: 1760389824}}, // Last BPO1 block
|
||||
{123, 1760389824, ID{Hash: checksumToBytes(0x9bc6cb31), Next: 0}}, // First BPO2 block
|
||||
{123, 2000000000, ID{Hash: checksumToBytes(0x9bc6cb31), Next: 0}}, // Future BPO1 block
|
||||
},
|
||||
},
|
||||
// Hoodi test cases
|
||||
|
|
@ -121,8 +133,14 @@ func TestCreation(t *testing.T) {
|
|||
[]testcase{
|
||||
{0, 0, ID{Hash: checksumToBytes(0xbef71d30), Next: 1742999832}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin, London, Paris, Shanghai, Cancun block
|
||||
{123, 1742999831, ID{Hash: checksumToBytes(0xbef71d30), Next: 1742999832}}, // Last Cancun block
|
||||
{123, 1742999832, ID{Hash: checksumToBytes(0x0929e24e), Next: 0}}, // First Prague block
|
||||
{123, 2740434112, ID{Hash: checksumToBytes(0x0929e24e), Next: 0}}, // Future Prague block
|
||||
{123, 1742999832, ID{Hash: checksumToBytes(0x0929e24e), Next: 1761677592}}, // First Prague block
|
||||
{123, 1761677591, ID{Hash: checksumToBytes(0x0929e24e), Next: 1761677592}}, // Last Prague block
|
||||
{123, 1761677592, ID{Hash: checksumToBytes(0xe7e0e7ff), Next: 1762365720}}, // First Osaka block
|
||||
{123, 1762365719, ID{Hash: checksumToBytes(0xe7e0e7ff), Next: 1762365720}}, // Last Osaka block
|
||||
{123, 1762365720, ID{Hash: checksumToBytes(0x3893353e), Next: 1762955544}}, // First BPO1 block
|
||||
{123, 1762955543, ID{Hash: checksumToBytes(0x3893353e), Next: 1762955544}}, // Last BPO1 block
|
||||
{123, 1762955544, ID{Hash: checksumToBytes(0x23aa1351), Next: 0}}, // First BPO2 block
|
||||
{123, 2000000000, ID{Hash: checksumToBytes(0x23aa1351), Next: 0}}, // Future BPO2 block
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ func (e *GenesisMismatchError) Error() string {
|
|||
// ChainOverrides contains the changes to chain config.
|
||||
type ChainOverrides struct {
|
||||
OverrideOsaka *uint64
|
||||
OverrideBPO1 *uint64
|
||||
OverrideBPO2 *uint64
|
||||
OverrideVerkle *uint64
|
||||
}
|
||||
|
||||
|
|
@ -270,6 +272,12 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
|
|||
if o.OverrideOsaka != nil {
|
||||
cfg.OsakaTime = o.OverrideOsaka
|
||||
}
|
||||
if o.OverrideBPO1 != nil {
|
||||
cfg.BPO1Time = o.OverrideBPO1
|
||||
}
|
||||
if o.OverrideBPO2 != nil {
|
||||
cfg.BPO2Time = o.OverrideBPO2
|
||||
}
|
||||
if o.OverrideVerkle != nil {
|
||||
cfg.VerkleTime = o.OverrideVerkle
|
||||
}
|
||||
|
|
@ -514,6 +522,11 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
|
|||
if head.BlobGasUsed == nil {
|
||||
head.BlobGasUsed = new(uint64)
|
||||
}
|
||||
} else {
|
||||
if g.ExcessBlobGas != nil {
|
||||
log.Warn("Invalid genesis, unexpected ExcessBlobGas set before Cancun, allowing it for testing purposes")
|
||||
head.ExcessBlobGas = g.ExcessBlobGas
|
||||
}
|
||||
}
|
||||
if conf.IsPrague(num, g.Timestamp) {
|
||||
head.RequestsHash = &types.EmptyRequestsHash
|
||||
|
|
@ -656,23 +669,24 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
|
|||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
Difficulty: big.NewInt(0),
|
||||
Alloc: map[common.Address]types.Account{
|
||||
common.BytesToAddress([]byte{0x01}): {Balance: big.NewInt(1)}, // ECRecover
|
||||
common.BytesToAddress([]byte{0x02}): {Balance: big.NewInt(1)}, // SHA256
|
||||
common.BytesToAddress([]byte{0x03}): {Balance: big.NewInt(1)}, // RIPEMD
|
||||
common.BytesToAddress([]byte{0x04}): {Balance: big.NewInt(1)}, // Identity
|
||||
common.BytesToAddress([]byte{0x05}): {Balance: big.NewInt(1)}, // ModExp
|
||||
common.BytesToAddress([]byte{0x06}): {Balance: big.NewInt(1)}, // ECAdd
|
||||
common.BytesToAddress([]byte{0x07}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||
common.BytesToAddress([]byte{0x08}): {Balance: big.NewInt(1)}, // ECPairing
|
||||
common.BytesToAddress([]byte{0x09}): {Balance: big.NewInt(1)}, // BLAKE2b
|
||||
common.BytesToAddress([]byte{0x0a}): {Balance: big.NewInt(1)}, // KZGPointEval
|
||||
common.BytesToAddress([]byte{0x0b}): {Balance: big.NewInt(1)}, // BLSG1Add
|
||||
common.BytesToAddress([]byte{0x0c}): {Balance: big.NewInt(1)}, // BLSG1MultiExp
|
||||
common.BytesToAddress([]byte{0x0d}): {Balance: big.NewInt(1)}, // BLSG2Add
|
||||
common.BytesToAddress([]byte{0x0e}): {Balance: big.NewInt(1)}, // BLSG2MultiExp
|
||||
common.BytesToAddress([]byte{0x0f}): {Balance: big.NewInt(1)}, // BLSG1Pairing
|
||||
common.BytesToAddress([]byte{0x10}): {Balance: big.NewInt(1)}, // BLSG1MapG1
|
||||
common.BytesToAddress([]byte{0x11}): {Balance: big.NewInt(1)}, // BLSG2MapG2
|
||||
common.BytesToAddress([]byte{0x01}): {Balance: big.NewInt(1)}, // ECRecover
|
||||
common.BytesToAddress([]byte{0x02}): {Balance: big.NewInt(1)}, // SHA256
|
||||
common.BytesToAddress([]byte{0x03}): {Balance: big.NewInt(1)}, // RIPEMD
|
||||
common.BytesToAddress([]byte{0x04}): {Balance: big.NewInt(1)}, // Identity
|
||||
common.BytesToAddress([]byte{0x05}): {Balance: big.NewInt(1)}, // ModExp
|
||||
common.BytesToAddress([]byte{0x06}): {Balance: big.NewInt(1)}, // ECAdd
|
||||
common.BytesToAddress([]byte{0x07}): {Balance: big.NewInt(1)}, // ECScalarMul
|
||||
common.BytesToAddress([]byte{0x08}): {Balance: big.NewInt(1)}, // ECPairing
|
||||
common.BytesToAddress([]byte{0x09}): {Balance: big.NewInt(1)}, // BLAKE2b
|
||||
common.BytesToAddress([]byte{0x0a}): {Balance: big.NewInt(1)}, // KZGPointEval
|
||||
common.BytesToAddress([]byte{0x0b}): {Balance: big.NewInt(1)}, // BLSG1Add
|
||||
common.BytesToAddress([]byte{0x0c}): {Balance: big.NewInt(1)}, // BLSG1MultiExp
|
||||
common.BytesToAddress([]byte{0x0d}): {Balance: big.NewInt(1)}, // BLSG2Add
|
||||
common.BytesToAddress([]byte{0x0e}): {Balance: big.NewInt(1)}, // BLSG2MultiExp
|
||||
common.BytesToAddress([]byte{0x0f}): {Balance: big.NewInt(1)}, // BLSG1Pairing
|
||||
common.BytesToAddress([]byte{0x10}): {Balance: big.NewInt(1)}, // BLSG1MapG1
|
||||
common.BytesToAddress([]byte{0x11}): {Balance: big.NewInt(1)}, // BLSG2MapG2
|
||||
common.BytesToAddress([]byte{0x1, 00}): {Balance: big.NewInt(1)}, // P256Verify
|
||||
// Pre-deploy system contracts
|
||||
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0},
|
||||
params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0},
|
||||
|
|
|
|||
|
|
@ -664,15 +664,6 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
|
|||
}
|
||||
}
|
||||
|
||||
// storedReceiptRLP is the storage encoding of a receipt.
|
||||
// Re-definition in core/types/receipt.go.
|
||||
// TODO: Re-use the existing definition.
|
||||
type storedReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*types.Log
|
||||
}
|
||||
|
||||
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
|
||||
// the list of logs. When decoding a stored receipt into this object we
|
||||
// avoid creating the bloom filter.
|
||||
|
|
@ -682,11 +673,11 @@ type receiptLogs struct {
|
|||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
|
||||
var stored storedReceiptRLP
|
||||
if err := s.Decode(&stored); err != nil {
|
||||
var rs types.ReceiptForStorage
|
||||
if err := rs.DecodeRLP(s); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Logs = stored.Logs
|
||||
r.Logs = rs.Logs
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ func TestBadBlockStorage(t *testing.T) {
|
|||
}
|
||||
for i := 0; i < len(badBlocks)-1; i++ {
|
||||
if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() {
|
||||
t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64())
|
||||
t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, badBlocks[i].NumberU64(), i+1, badBlocks[i+1].NumberU64())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,27 @@ func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) {
|
|||
}
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryIndexMetadata retrieves the metadata of trienode history index.
|
||||
func ReadTrienodeHistoryIndexMetadata(db ethdb.KeyValueReader) []byte {
|
||||
data, _ := db.Get(headTrienodeHistoryIndexKey)
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrienodeHistoryIndexMetadata stores the metadata of trienode history index
|
||||
// into database.
|
||||
func WriteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) {
|
||||
if err := db.Put(headTrienodeHistoryIndexKey, blob); err != nil {
|
||||
log.Crit("Failed to store the metadata of trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndexMetadata removes the metadata of trienode history index.
|
||||
func DeleteTrienodeHistoryIndexMetadata(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(headTrienodeHistoryIndexKey); err != nil {
|
||||
log.Crit("Failed to delete the metadata of trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAccountHistoryIndex retrieves the account history index with the provided
|
||||
// account address.
|
||||
func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte {
|
||||
|
|
@ -95,6 +116,30 @@ func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash,
|
|||
}
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryIndex retrieves the trienode history index with the provided
|
||||
// account address and storage key hash.
|
||||
func ReadTrienodeHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, path []byte) []byte {
|
||||
data, err := db.Get(trienodeHistoryIndexKey(addressHash, path))
|
||||
if err != nil || len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrienodeHistoryIndex writes the provided trienode history index into database.
|
||||
func WriteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, data []byte) {
|
||||
if err := db.Put(trienodeHistoryIndexKey(addressHash, path), data); err != nil {
|
||||
log.Crit("Failed to store trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndex deletes the specified trienode index from the database.
|
||||
func DeleteTrienodeHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte) {
|
||||
if err := db.Delete(trienodeHistoryIndexKey(addressHash, path)); err != nil {
|
||||
log.Crit("Failed to delete trienode history index", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAccountHistoryIndexBlock retrieves the index block with the provided
|
||||
// account address along with the block id.
|
||||
func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte {
|
||||
|
|
@ -143,6 +188,30 @@ func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.
|
|||
}
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryIndexBlock retrieves the index block with the provided state
|
||||
// identifier along with the block id.
|
||||
func ReadTrienodeHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, path []byte, blockID uint32) []byte {
|
||||
data, err := db.Get(trienodeHistoryIndexBlockKey(addressHash, path, blockID))
|
||||
if err != nil || len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTrienodeHistoryIndexBlock writes the provided index block into database.
|
||||
func WriteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32, data []byte) {
|
||||
if err := db.Put(trienodeHistoryIndexBlockKey(addressHash, path, id), data); err != nil {
|
||||
log.Crit("Failed to store trienode index block", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndexBlock deletes the specified index block from the database.
|
||||
func DeleteTrienodeHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, path []byte, id uint32) {
|
||||
if err := db.Delete(trienodeHistoryIndexBlockKey(addressHash, path, id)); err != nil {
|
||||
log.Crit("Failed to delete trienode index block", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// increaseKey increase the input key by one bit. Return nil if the entire
|
||||
// addition operation overflows.
|
||||
func increaseKey(key []byte) []byte {
|
||||
|
|
@ -155,14 +224,26 @@ func increaseKey(key []byte) []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeleteStateHistoryIndex completely removes all history indexing data, including
|
||||
// DeleteStateHistoryIndexes completely removes all history indexing data, including
|
||||
// indexes for accounts and storages.
|
||||
//
|
||||
// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix`
|
||||
// is exclusively occupied by the history indexing data!
|
||||
func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) {
|
||||
start := StateHistoryIndexPrefix
|
||||
limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix))
|
||||
func DeleteStateHistoryIndexes(db ethdb.KeyValueRangeDeleter) {
|
||||
DeleteHistoryByRange(db, StateHistoryAccountMetadataPrefix)
|
||||
DeleteHistoryByRange(db, StateHistoryStorageMetadataPrefix)
|
||||
DeleteHistoryByRange(db, StateHistoryAccountBlockPrefix)
|
||||
DeleteHistoryByRange(db, StateHistoryStorageBlockPrefix)
|
||||
}
|
||||
|
||||
// DeleteTrienodeHistoryIndexes completely removes all trienode history indexing data.
|
||||
func DeleteTrienodeHistoryIndexes(db ethdb.KeyValueRangeDeleter) {
|
||||
DeleteHistoryByRange(db, TrienodeHistoryMetadataPrefix)
|
||||
DeleteHistoryByRange(db, TrienodeHistoryBlockPrefix)
|
||||
}
|
||||
|
||||
// DeleteHistoryByRange completely removes all database entries with the specific prefix.
|
||||
// Note, this method assumes the space with the given prefix is exclusively occupied!
|
||||
func DeleteHistoryByRange(db ethdb.KeyValueRangeDeleter, prefix []byte) {
|
||||
start := prefix
|
||||
limit := increaseKey(bytes.Clone(prefix))
|
||||
|
||||
// Try to remove the data in the range by a loop, as the leveldb
|
||||
// doesn't support the native range deletion.
|
||||
|
|
|
|||
|
|
@ -170,9 +170,11 @@ func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint
|
|||
return db.AncientRange(stateHistoryMeta, start-1, count, 0)
|
||||
}
|
||||
|
||||
// ReadStateAccountIndex retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
// ReadStateAccountIndex retrieves the account index blob for the specified
|
||||
// state history. The index contains fixed-size entries with offsets and lengths
|
||||
// into the concatenated account data table. Compute the position of state
|
||||
// history in freezer by minus one since the id of first state history starts
|
||||
// from one (zero for initial state).
|
||||
func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryAccountIndex, id-1)
|
||||
if err != nil {
|
||||
|
|
@ -181,37 +183,30 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
|||
return blob
|
||||
}
|
||||
|
||||
// ReadStateStorageIndex retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageIndex, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
// ReadStateStorageIndex retrieves the storage index blob for the specified
|
||||
// state history. The index contains fixed-size entries that locate storage slot
|
||||
// data in the concatenated storage data table. Compute the position of state
|
||||
// history in freezer by minus one since the id of first state history starts
|
||||
// from one (zero for initial state).
|
||||
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// ReadStateAccountHistory retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryAccountData, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
// ReadStateAccountHistory retrieves the concatenated account data blob for the
|
||||
// specified state history. Offsets and lengths are resolved via the account
|
||||
// index. Compute the position of state history in freezer by minus one since
|
||||
// the id of first state history starts from one (zero for initial state).
|
||||
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// ReadStateStorageHistory retrieves the state root corresponding to the specified
|
||||
// state history. Compute the position of state history in freezer by minus one
|
||||
// since the id of first state history starts from one(zero for initial state).
|
||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte {
|
||||
blob, err := db.Ancient(stateHistoryStorageData, id-1)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return blob
|
||||
// ReadStateStorageHistory retrieves the concatenated storage slot data blob for
|
||||
// the specified state history. Locations are resolved via the account and
|
||||
// storage indexes. Compute the position of state history in freezer by minus
|
||||
// one since the id of first state history starts from one (zero for initial
|
||||
// state).
|
||||
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
|
||||
return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length))
|
||||
}
|
||||
|
||||
// ReadStateHistory retrieves the state history from database with provided id.
|
||||
|
|
@ -292,3 +287,76 @@ func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIn
|
|||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadTrienodeHistory retrieves the trienode history corresponding to the specified id.
|
||||
// Compute the position of trienode history in freezer by minus one since the id of first
|
||||
// trienode history starts from one(zero for initial state).
|
||||
func ReadTrienodeHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, error) {
|
||||
header, err := db.Ancient(trienodeHistoryHeaderTable, id-1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keySection, err := db.Ancient(trienodeHistoryKeySectionTable, id-1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
valueSection, err := db.Ancient(trienodeHistoryValueSectionTable, id-1)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return header, keySection, valueSection, nil
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryHeader retrieves the header section of trienode history.
|
||||
func ReadTrienodeHistoryHeader(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
|
||||
return db.Ancient(trienodeHistoryHeaderTable, id-1)
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryKeySection retrieves the key section of trienode history.
|
||||
func ReadTrienodeHistoryKeySection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
|
||||
return db.Ancient(trienodeHistoryKeySectionTable, id-1)
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryValueSection retrieves the value section of trienode history.
|
||||
func ReadTrienodeHistoryValueSection(db ethdb.AncientReaderOp, id uint64) ([]byte, error) {
|
||||
return db.Ancient(trienodeHistoryValueSectionTable, id-1)
|
||||
}
|
||||
|
||||
// ReadTrienodeHistoryList retrieves the a list of trienode history corresponding
|
||||
// to the specified range.
|
||||
// Compute the position of trienode history in freezer by minus one since the id
|
||||
// of first trienode history starts from one(zero for initial state).
|
||||
func ReadTrienodeHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, error) {
|
||||
header, err := db.AncientRange(trienodeHistoryHeaderTable, start-1, count, 0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
keySection, err := db.AncientRange(trienodeHistoryKeySectionTable, start-1, count, 0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
valueSection, err := db.AncientRange(trienodeHistoryValueSectionTable, start-1, count, 0)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if len(header) != len(keySection) || len(header) != len(valueSection) {
|
||||
return nil, nil, nil, errors.New("trienode history is corrupted")
|
||||
}
|
||||
return header, keySection, valueSection, nil
|
||||
}
|
||||
|
||||
// WriteTrienodeHistory writes the provided trienode history to database.
|
||||
// Compute the position of trienode history in freezer by minus one since
|
||||
// the id of first state history starts from one(zero for initial state).
|
||||
func WriteTrienodeHistory(db ethdb.AncientWriter, id uint64, header []byte, keySection []byte, valueSection []byte) error {
|
||||
_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
if err := op.AppendRaw(trienodeHistoryHeaderTable, id-1, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := op.AppendRaw(trienodeHistoryKeySectionTable, id-1, keySection); err != nil {
|
||||
return err
|
||||
}
|
||||
return op.AppendRaw(trienodeHistoryValueSectionTable, id-1, valueSection)
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,15 +75,38 @@ var stateFreezerTableConfigs = map[string]freezerTableConfig{
|
|||
stateHistoryStorageData: {noSnappy: false, prunable: true},
|
||||
}
|
||||
|
||||
const (
|
||||
trienodeHistoryHeaderTable = "trienode.header"
|
||||
trienodeHistoryKeySectionTable = "trienode.key"
|
||||
trienodeHistoryValueSectionTable = "trienode.value"
|
||||
)
|
||||
|
||||
// trienodeFreezerTableConfigs configures the settings for tables in the trienode freezer.
|
||||
var trienodeFreezerTableConfigs = map[string]freezerTableConfig{
|
||||
trienodeHistoryHeaderTable: {noSnappy: false, prunable: true},
|
||||
|
||||
// Disable snappy compression to allow efficient partial read.
|
||||
trienodeHistoryKeySectionTable: {noSnappy: true, prunable: true},
|
||||
|
||||
// Disable snappy compression to allow efficient partial read.
|
||||
trienodeHistoryValueSectionTable: {noSnappy: true, prunable: true},
|
||||
}
|
||||
|
||||
// The list of identifiers of ancient stores.
|
||||
var (
|
||||
ChainFreezerName = "chain" // the folder name of chain segment ancient store.
|
||||
MerkleStateFreezerName = "state" // the folder name of state history ancient store.
|
||||
VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store.
|
||||
ChainFreezerName = "chain" // the folder name of chain segment ancient store.
|
||||
MerkleStateFreezerName = "state" // the folder name of state history ancient store.
|
||||
VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store.
|
||||
MerkleTrienodeFreezerName = "trienode" // the folder name of trienode history ancient store.
|
||||
VerkleTrienodeFreezerName = "trienode_verkle" // the folder name of trienode history ancient store.
|
||||
)
|
||||
|
||||
// freezers the collections of all builtin freezers.
|
||||
var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFreezerName}
|
||||
var freezers = []string{
|
||||
ChainFreezerName,
|
||||
MerkleStateFreezerName, VerkleStateFreezerName,
|
||||
MerkleTrienodeFreezerName, VerkleTrienodeFreezerName,
|
||||
}
|
||||
|
||||
// NewStateFreezer initializes the ancient store for state history.
|
||||
//
|
||||
|
|
@ -103,3 +126,22 @@ func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.Reset
|
|||
}
|
||||
return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerTableConfigs)
|
||||
}
|
||||
|
||||
// NewTrienodeFreezer initializes the ancient store for trienode history.
|
||||
//
|
||||
// - if the empty directory is given, initializes the pure in-memory
|
||||
// trienode freezer (e.g. dev mode).
|
||||
// - if non-empty directory is given, initializes the regular file-based
|
||||
// trienode freezer.
|
||||
func NewTrienodeFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.ResettableAncientStore, error) {
|
||||
if ancientDir == "" {
|
||||
return NewMemoryFreezer(readOnly, trienodeFreezerTableConfigs), nil
|
||||
}
|
||||
var name string
|
||||
if verkle {
|
||||
name = filepath.Join(ancientDir, VerkleTrienodeFreezerName)
|
||||
} else {
|
||||
name = filepath.Join(ancientDir, MerkleTrienodeFreezerName)
|
||||
}
|
||||
return newResettableFreezer(name, "eth/db/trienode", readOnly, stateHistoryTableSize, trienodeFreezerTableConfigs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64)
|
|||
return f.ancients.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
return f.ancients.AncientBytes(kind, id, offset, length)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
|
||||
return f.ancients.ModifyAncients(fn)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64)
|
|||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// Ancients returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) Ancients() (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ type Freezer struct {
|
|||
// NewFreezer creates a freezer instance for maintaining immutable ordered
|
||||
// data according to the given parameters.
|
||||
//
|
||||
// The 'tables' argument defines the data tables. If the value of a map
|
||||
// entry is true, snappy compression is disabled for the table.
|
||||
// The 'tables' argument defines the freezer tables and their configuration.
|
||||
// Each value is a freezerTableConfig specifying whether snappy compression is
|
||||
// disabled (noSnappy) and whether the table is prunable (prunable).
|
||||
func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) {
|
||||
// Create the initial freezer object
|
||||
var (
|
||||
|
|
@ -201,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
|
|||
return nil, errUnknownTable
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
if table := f.tables[kind]; table != nil {
|
||||
return table.RetrieveBytes(id, offset, length)
|
||||
}
|
||||
return nil, errUnknownTable
|
||||
}
|
||||
|
||||
// Ancients returns the length of the frozen items.
|
||||
func (f *Freezer) Ancients() (uint64, error) {
|
||||
return f.frozen.Load(), nil
|
||||
|
|
|
|||
|
|
@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error {
|
|||
func (f *MemoryFreezer) AncientDatadir() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
table := f.tables[kind]
|
||||
if table == nil {
|
||||
return nil, errUnknownTable
|
||||
}
|
||||
entries, err := table.retrieve(id, 1, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return nil, errOutOfBounds
|
||||
}
|
||||
data := entries[0]
|
||||
|
||||
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
|
||||
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
|
||||
}
|
||||
return data[offset : offset+length], nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin
|
|||
return f.freezer.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
// AncientBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.AncientBytes(kind, id, offset, length)
|
||||
}
|
||||
|
||||
// Ancients returns the length of the frozen items.
|
||||
func (f *resettableFreezer) Ancients() (uint64, error) {
|
||||
f.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ type freezerTable struct {
|
|||
// should never be lower than itemOffset.
|
||||
itemHidden atomic.Uint64
|
||||
|
||||
config freezerTableConfig // if true, disables snappy compression. Note: does not work retroactively
|
||||
config freezerTableConfig // table configuration (compression, prunability). Note: compression flag does not apply retroactively to existing files
|
||||
readonly bool
|
||||
maxFileSize uint32 // Max file size for data-files
|
||||
name string
|
||||
|
|
@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
|
|||
return output, sizes, nil
|
||||
}
|
||||
|
||||
// RetrieveBytes retrieves the value segment of the element specified by the id
|
||||
// and value offsets.
|
||||
func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
if t.index == nil || t.head == nil || t.metadata.file == nil {
|
||||
return nil, errClosed
|
||||
}
|
||||
items, hidden := t.items.Load(), t.itemHidden.Load()
|
||||
if items <= item || hidden > item {
|
||||
return nil, errOutOfBounds
|
||||
}
|
||||
|
||||
// Retrieves the index entries for the specified ID and its immediate successor
|
||||
indices, err := t.getIndices(item, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index0, index1 := indices[0], indices[1]
|
||||
|
||||
itemStart, itemLimit, fileId := index0.bounds(index1)
|
||||
itemSize := itemLimit - itemStart
|
||||
|
||||
dataFile, exist := t.files[fileId]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("missing data file %d", fileId)
|
||||
}
|
||||
|
||||
// Perform the partial read if no-compression was enabled upon
|
||||
if t.config.noSnappy {
|
||||
if offset > uint64(itemSize) || offset+length > uint64(itemSize) {
|
||||
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length)
|
||||
}
|
||||
itemStart += uint32(offset)
|
||||
|
||||
buf := make([]byte, length)
|
||||
_, err = dataFile.ReadAt(buf, int64(itemStart))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.readMeter.Mark(int64(length))
|
||||
return buf, nil
|
||||
} else {
|
||||
// If compressed, read the full item, decompress, then slice.
|
||||
// Unfortunately, in this case, there is no performance gain
|
||||
// by performing the partial read at all.
|
||||
buf := make([]byte, itemSize)
|
||||
_, err = dataFile.ReadAt(buf, int64(itemStart))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.readMeter.Mark(int64(itemSize))
|
||||
|
||||
data, err := snappy.Decode(nil, buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
|
||||
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
|
||||
}
|
||||
return data[offset : offset+length], nil
|
||||
}
|
||||
}
|
||||
|
||||
// size returns the total data size in the freezer table.
|
||||
func (t *freezerTable) size() (uint64, error) {
|
||||
t.lock.RLock()
|
||||
|
|
|
|||
|
|
@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) {
|
|||
t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFreezerAncientBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
types := []struct {
|
||||
name string
|
||||
config freezerTableConfig
|
||||
}{
|
||||
{"uncompressed", freezerTableConfig{noSnappy: true}},
|
||||
{"compressed", freezerTableConfig{noSnappy: false}},
|
||||
}
|
||||
for _, typ := range types {
|
||||
t.Run(typ.name, func(t *testing.T) {
|
||||
f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
data := getChunk(100, i)
|
||||
batch := f.newBatch()
|
||||
require.NoError(t, batch.AppendRaw(uint64(i), data))
|
||||
require.NoError(t, batch.commit())
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
full, err := f.Retrieve(uint64(i))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Full read
|
||||
got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full)))
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full) {
|
||||
t.Fatalf("full read mismatch for entry %d", i)
|
||||
}
|
||||
// Empty read
|
||||
got, err = f.RetrieveBytes(uint64(i), 0, 0)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full[:0]) {
|
||||
t.Fatalf("empty read mismatch for entry %d", i)
|
||||
}
|
||||
// Middle slice
|
||||
got, err = f.RetrieveBytes(uint64(i), 10, 50)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full[10:60]) {
|
||||
t.Fatalf("middle slice mismatch for entry %d", i)
|
||||
}
|
||||
// Single byte
|
||||
got, err = f.RetrieveBytes(uint64(i), 99, 1)
|
||||
require.NoError(t, err)
|
||||
if !bytes.Equal(got, full[99:100]) {
|
||||
t.Fatalf("single byte mismatch for entry %d", i)
|
||||
}
|
||||
// Out of bounds
|
||||
_, err = f.RetrieveBytes(uint64(i), 100, 1)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for out-of-bounds read for entry %d", i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) {
|
|||
// fails, otherwise it succeeds. In either case, the freezer should be positioned
|
||||
// at 10 after both operations are done.
|
||||
if truncateErr != nil {
|
||||
t.Fatal("concurrent truncate failed:", err)
|
||||
t.Fatal("concurrent truncate failed:", truncateErr)
|
||||
}
|
||||
if !(errors.Is(modifyErr, nil) || errors.Is(modifyErr, errOutOrderInsertion)) {
|
||||
t.Fatal("wrong error from concurrent modify:", modifyErr)
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ var (
|
|||
// been indexed.
|
||||
headStateHistoryIndexKey = []byte("LastStateHistoryIndex")
|
||||
|
||||
// headTrienodeHistoryIndexKey tracks the ID of the latest state history that has
|
||||
// been indexed.
|
||||
headTrienodeHistoryIndexKey = []byte("LastTrienodeHistoryIndex")
|
||||
|
||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||
txIndexTailKey = []byte("TransactionIndexTail")
|
||||
|
||||
|
|
@ -125,8 +129,10 @@ var (
|
|||
StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data
|
||||
StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata
|
||||
StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata
|
||||
TrienodeHistoryMetadataPrefix = []byte("mt") // TrienodeHistoryMetadataPrefix + account address hash + trienode path => trienode metadata
|
||||
StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + blockID => account block
|
||||
StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + blockID => slot block
|
||||
TrienodeHistoryBlockPrefix = []byte("mbt") // TrienodeHistoryBlockPrefix + account address hash + trienode path + blockID => trienode block
|
||||
|
||||
// VerklePrefix is the database prefix for Verkle trie data, which includes:
|
||||
// (a) Trie nodes
|
||||
|
|
@ -395,27 +401,34 @@ func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []
|
|||
return out
|
||||
}
|
||||
|
||||
// trienodeHistoryIndexKey = TrienodeHistoryMetadataPrefix + addressHash + trienode path
|
||||
func trienodeHistoryIndexKey(addressHash common.Hash, path []byte) []byte {
|
||||
totalLen := len(TrienodeHistoryMetadataPrefix) + common.HashLength + len(path)
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
off := 0
|
||||
off += copy(out[off:], TrienodeHistoryMetadataPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
copy(out[off:], path)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID
|
||||
func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte {
|
||||
var buf4 [4]byte
|
||||
binary.BigEndian.PutUint32(buf4[:], blockID)
|
||||
|
||||
totalLen := len(StateHistoryAccountBlockPrefix) + common.HashLength + 4
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
off := 0
|
||||
off += copy(out[off:], StateHistoryAccountBlockPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
copy(out[off:], buf4[:])
|
||||
binary.BigEndian.PutUint32(out[off:], blockID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID
|
||||
func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
|
||||
var buf4 [4]byte
|
||||
binary.BigEndian.PutUint32(buf4[:], blockID)
|
||||
|
||||
totalLen := len(StateHistoryStorageBlockPrefix) + 2*common.HashLength + 4
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
|
|
@ -423,7 +436,21 @@ func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Has
|
|||
off += copy(out[off:], StateHistoryStorageBlockPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
off += copy(out[off:], storageHash.Bytes())
|
||||
copy(out[off:], buf4[:])
|
||||
binary.BigEndian.PutUint32(out[off:], blockID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// trienodeHistoryIndexBlockKey = TrienodeHistoryBlockPrefix + addressHash + trienode path + blockID
|
||||
func trienodeHistoryIndexBlockKey(addressHash common.Hash, path []byte, blockID uint32) []byte {
|
||||
totalLen := len(TrienodeHistoryBlockPrefix) + common.HashLength + len(path) + 4
|
||||
out := make([]byte, totalLen)
|
||||
|
||||
off := 0
|
||||
off += copy(out[off:], TrienodeHistoryBlockPrefix)
|
||||
off += copy(out[off:], addressHash.Bytes())
|
||||
off += copy(out[off:], path)
|
||||
binary.BigEndian.PutUint32(out[off:], blockID)
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by
|
|||
return t.db.AncientRange(kind, start, count, maxBytes)
|
||||
}
|
||||
|
||||
// AncientBytes is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
|
||||
return t.db.AncientBytes(kind, id, offset, length)
|
||||
}
|
||||
|
||||
// Ancients is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) Ancients() (uint64, error) {
|
||||
|
|
|
|||
|
|
@ -149,8 +149,7 @@ func BenchmarkHashing(b *testing.B) {
|
|||
var got common.Hash
|
||||
var hasher = sha3.NewLegacyKeccak256()
|
||||
b.Run("iteratorhashing", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
var hash common.Hash
|
||||
it, err := rlp.NewListIterator(bodyRlp)
|
||||
if err != nil {
|
||||
|
|
@ -172,8 +171,7 @@ func BenchmarkHashing(b *testing.B) {
|
|||
})
|
||||
var exp common.Hash
|
||||
b.Run("fullbodyhashing", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
var body types.Body
|
||||
rlp.DecodeBytes(bodyRlp, &body)
|
||||
for _, tx := range body.Transactions {
|
||||
|
|
@ -182,8 +180,7 @@ func BenchmarkHashing(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("fullblockhashing", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
var block types.Block
|
||||
rlp.DecodeBytes(blockRlp, &block)
|
||||
for _, tx := range block.Transactions() {
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ func TestMessageCallGas(t *testing.T) {
|
|||
}
|
||||
gas = ae.CodeHashGas(testAddr, false, math.MaxUint64, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
|
||||
// Check warm read cost
|
||||
|
|
|
|||
|
|
@ -302,6 +302,8 @@ func mustCopyTrie(t Trie) Trie {
|
|||
return t.Copy()
|
||||
case *trie.VerkleTrie:
|
||||
return t.Copy()
|
||||
case *trie.TransitionTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -229,8 +229,7 @@ func BenchmarkSearch(b *testing.B) {
|
|||
layer = fill(layer)
|
||||
}
|
||||
key := crypto.Keccak256Hash([]byte{0x13, 0x38})
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
layer.AccountRLP(key)
|
||||
}
|
||||
}
|
||||
|
|
@ -269,8 +268,7 @@ func BenchmarkSearchSlot(b *testing.B) {
|
|||
for i := 0; i < 128; i++ {
|
||||
layer = fill(layer)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
layer.Storage(accountKey, storageKey)
|
||||
}
|
||||
}
|
||||
|
|
@ -300,9 +298,7 @@ func BenchmarkFlatten(b *testing.B) {
|
|||
}
|
||||
return newDiffLayer(parent, common.Hash{}, accounts, storage)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
for b.Loop() {
|
||||
var layer snapshot
|
||||
layer = emptyLayer()
|
||||
for i := 1; i < 128; i++ {
|
||||
|
|
@ -352,9 +348,7 @@ func BenchmarkJournal(b *testing.B) {
|
|||
for i := 1; i < 128; i++ {
|
||||
layer = fill(layer)
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
layer.Journal(new(bytes.Buffer))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -928,7 +928,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) {
|
|||
head.(*diffLayer).newBinaryAccountIterator(common.Hash{})
|
||||
|
||||
b.Run("binary iterator keys", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
got := 0
|
||||
it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{})
|
||||
for it.Next() {
|
||||
|
|
@ -940,7 +940,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("binary iterator values", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
got := 0
|
||||
it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{})
|
||||
for it.Next() {
|
||||
|
|
@ -953,7 +953,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("fast iterator keys", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{})
|
||||
defer it.Release()
|
||||
|
||||
|
|
@ -967,7 +967,7 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("fast iterator values", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{})
|
||||
defer it.Release()
|
||||
|
||||
|
|
@ -1025,7 +1025,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) {
|
|||
head.(*diffLayer).newBinaryAccountIterator(common.Hash{})
|
||||
|
||||
b.Run("binary iterator (keys)", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
got := 0
|
||||
it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{})
|
||||
for it.Next() {
|
||||
|
|
@ -1037,7 +1037,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("binary iterator (values)", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
got := 0
|
||||
it := head.(*diffLayer).newBinaryAccountIterator(common.Hash{})
|
||||
for it.Next() {
|
||||
|
|
@ -1051,7 +1051,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("fast iterator (keys)", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{})
|
||||
defer it.Release()
|
||||
|
||||
|
|
@ -1065,7 +1065,7 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) {
|
|||
}
|
||||
})
|
||||
b.Run("fast iterator (values)", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{})
|
||||
defer it.Release()
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
|
@ -333,7 +334,7 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
continue
|
||||
}
|
||||
if !exist {
|
||||
log.Error("Storage slot is not found in pending area", s.address, "slot", key)
|
||||
log.Error("Storage slot is not found in pending area", "address", s.address, "slot", key)
|
||||
continue
|
||||
}
|
||||
if (value != common.Hash{}) {
|
||||
|
|
@ -494,8 +495,20 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
|
|||
selfDestructed: s.selfDestructed,
|
||||
newContract: s.newContract,
|
||||
}
|
||||
if s.trie != nil {
|
||||
|
||||
switch s.trie.(type) {
|
||||
case *trie.VerkleTrie:
|
||||
// Verkle uses only one tree, and the copy has already been
|
||||
// made in mustCopyTrie.
|
||||
obj.trie = db.trie
|
||||
case *trie.TransitionTrie:
|
||||
// Same thing for the transition tree, since the MPT is
|
||||
// read-only.
|
||||
obj.trie = db.trie
|
||||
case *trie.StateTrie:
|
||||
obj.trie = mustCopyTrie(s.trie)
|
||||
case nil:
|
||||
// do nothing
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
|
||||
func BenchmarkCutOriginal(b *testing.B) {
|
||||
value := common.HexToHash("0x01")
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
bytes.TrimLeft(value[:], "\x00")
|
||||
}
|
||||
}
|
||||
|
|
@ -33,14 +33,14 @@ func BenchmarkCutOriginal(b *testing.B) {
|
|||
func BenchmarkCutsetterFn(b *testing.B) {
|
||||
value := common.HexToHash("0x01")
|
||||
cutSetFn := func(r rune) bool { return r == 0 }
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
bytes.TrimLeftFunc(value[:], cutSetFn)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCutCustomTrim(b *testing.B) {
|
||||
value := common.HexToHash("0x01")
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
common.TrimLeftZeroes(value[:])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,14 @@ func TestSizeTracker(t *testing.T) {
|
|||
}
|
||||
baselineRoot := currentRoot
|
||||
|
||||
// Close and reopen the trie database so all async flushes triggered by the
|
||||
// baseline commits are written before we measure the baseline snapshot.
|
||||
if err := tdb.Close(); err != nil {
|
||||
t.Fatalf("Failed to close triedb before baseline measurement: %v", err)
|
||||
}
|
||||
tdb = triedb.NewDatabase(db, &triedb.Config{PathDB: pathdb.Defaults})
|
||||
sdb = NewDatabase(tdb, nil)
|
||||
|
||||
// Wait for snapshot completion
|
||||
for !tdb.SnapshotCompleted() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
|
@ -215,13 +223,12 @@ func TestSizeTracker(t *testing.T) {
|
|||
if actualStats.ContractCodeBytes != expectedStats.ContractCodeBytes {
|
||||
t.Errorf("Contract code bytes mismatch: expected %d, got %d", expectedStats.ContractCodeBytes, actualStats.ContractCodeBytes)
|
||||
}
|
||||
// TODO: failed on github actions, need to investigate
|
||||
// if actualStats.AccountTrienodes != expectedStats.AccountTrienodes {
|
||||
// t.Errorf("Account trie nodes mismatch: expected %d, got %d", expectedStats.AccountTrienodes, actualStats.AccountTrienodes)
|
||||
// }
|
||||
// if actualStats.AccountTrienodeBytes != expectedStats.AccountTrienodeBytes {
|
||||
// t.Errorf("Account trie node bytes mismatch: expected %d, got %d", expectedStats.AccountTrienodeBytes, actualStats.AccountTrienodeBytes)
|
||||
// }
|
||||
if actualStats.AccountTrienodes != expectedStats.AccountTrienodes {
|
||||
t.Errorf("Account trie nodes mismatch: expected %d, got %d", expectedStats.AccountTrienodes, actualStats.AccountTrienodes)
|
||||
}
|
||||
if actualStats.AccountTrienodeBytes != expectedStats.AccountTrienodeBytes {
|
||||
t.Errorf("Account trie node bytes mismatch: expected %d, got %d", expectedStats.AccountTrienodeBytes, actualStats.AccountTrienodeBytes)
|
||||
}
|
||||
if actualStats.StorageTrienodes != expectedStats.StorageTrienodes {
|
||||
t.Errorf("Storage trie nodes mismatch: expected %d, got %d", expectedStats.StorageTrienodes, actualStats.StorageTrienodes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -191,17 +191,18 @@ func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64, reason tr
|
|||
|
||||
func (s *hookedStateDB) SetCode(address common.Address, code []byte, reason tracing.CodeChangeReason) []byte {
|
||||
prev := s.inner.SetCode(address, code, reason)
|
||||
|
||||
if s.hooks.OnCodeChangeV2 != nil || s.hooks.OnCodeChange != nil {
|
||||
prevHash := types.EmptyCodeHash
|
||||
if len(prev) != 0 {
|
||||
prevHash = crypto.Keccak256Hash(prev)
|
||||
}
|
||||
prevHash := crypto.Keccak256Hash(prev)
|
||||
codeHash := crypto.Keccak256Hash(code)
|
||||
|
||||
if s.hooks.OnCodeChangeV2 != nil {
|
||||
s.hooks.OnCodeChangeV2(address, prevHash, prev, codeHash, code, reason)
|
||||
} else if s.hooks.OnCodeChange != nil {
|
||||
s.hooks.OnCodeChange(address, prevHash, prev, codeHash, code)
|
||||
// Invoke the hooks only if the contract code is changed
|
||||
if prevHash != codeHash {
|
||||
if s.hooks.OnCodeChangeV2 != nil {
|
||||
s.hooks.OnCodeChangeV2(address, prevHash, prev, codeHash, code, reason)
|
||||
} else if s.hooks.OnCodeChange != nil {
|
||||
s.hooks.OnCodeChange(address, prevHash, prev, codeHash, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
return prev
|
||||
|
|
|
|||
|
|
@ -772,7 +772,7 @@ func TestCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
// Commit state, ensure states can be loaded from disk
|
||||
root, _ := state.Commit(0, false, false)
|
||||
|
|
@ -859,7 +859,7 @@ func TestCopyCopyCommitCopy(t *testing.T) {
|
|||
t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
}
|
||||
if val := copyThree.GetCommittedState(addr, skey); val != (common.Hash{}) {
|
||||
t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval)
|
||||
t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -912,10 +912,10 @@ func TestCommitCopy(t *testing.T) {
|
|||
}
|
||||
// Slots cached in the stateDB, available after commit
|
||||
if val := copied.GetState(addr, skey2); val != sval2 {
|
||||
t.Fatalf("unexpected storage slot: have %x", sval1)
|
||||
t.Fatalf("unexpected storage slot: have %x, want %x", val, sval2)
|
||||
}
|
||||
if val := copied.GetCommittedState(addr, skey2); val != sval2 {
|
||||
t.Fatalf("unexpected storage slot: have %x", val)
|
||||
t.Fatalf("unexpected storage slot: have %x, want %x", val, sval2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,18 +35,21 @@ import (
|
|||
//
|
||||
// StateProcessor implements Processor.
|
||||
type StateProcessor struct {
|
||||
config *params.ChainConfig // Chain configuration options
|
||||
chain *HeaderChain // Canonical header chain
|
||||
chain ChainContext // Chain context interface
|
||||
}
|
||||
|
||||
// NewStateProcessor initialises a new StateProcessor.
|
||||
func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor {
|
||||
func NewStateProcessor(chain ChainContext) *StateProcessor {
|
||||
return &StateProcessor{
|
||||
config: config,
|
||||
chain: chain,
|
||||
chain: chain,
|
||||
}
|
||||
}
|
||||
|
||||
// chainConfig returns the chain configuration.
|
||||
func (p *StateProcessor) chainConfig() *params.ChainConfig {
|
||||
return p.chain.Config()
|
||||
}
|
||||
|
||||
// Process processes the state changes according to the Ethereum rules by running
|
||||
// the transaction messages using the statedb and applying any rewards to both
|
||||
// the processor (coinbase) and any included uncles.
|
||||
|
|
@ -56,6 +59,7 @@ func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StatePro
|
|||
// transactions failed to execute due to insufficient gas it will return an error.
|
||||
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
|
||||
var (
|
||||
config = p.chainConfig()
|
||||
receipts types.Receipts
|
||||
usedGas = new(uint64)
|
||||
header = block.Header()
|
||||
|
|
@ -66,12 +70,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
)
|
||||
|
||||
// Mutate the block and state according to any hard-fork specs
|
||||
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
var (
|
||||
context vm.BlockContext
|
||||
signer = types.MakeSigner(p.config, header.Number, header.Time)
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
)
|
||||
|
||||
// Apply pre-execution system calls.
|
||||
|
|
@ -80,12 +84,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
tracingStateDB = state.NewHookedState(statedb, hooks)
|
||||
}
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
evm := vm.NewEVM(context, tracingStateDB, p.config, cfg)
|
||||
evm := vm.NewEVM(context, tracingStateDB, config, cfg)
|
||||
|
||||
if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
|
||||
ProcessBeaconBlockRoot(*beaconRoot, evm)
|
||||
}
|
||||
if p.config.IsPrague(block.Number(), block.Time()) || p.config.IsVerkle(block.Number(), block.Time()) {
|
||||
if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) {
|
||||
ProcessParentBlockHash(block.ParentHash(), evm)
|
||||
}
|
||||
|
||||
|
|
@ -106,10 +110,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
// Read requests if Prague is enabled.
|
||||
var requests [][]byte
|
||||
if p.config.IsPrague(block.Number(), block.Time()) {
|
||||
if config.IsPrague(block.Number(), block.Time()) {
|
||||
requests = [][]byte{}
|
||||
// EIP-6110
|
||||
if err := ParseDepositLogs(&requests, allLogs, p.config); err != nil {
|
||||
if err := ParseDepositLogs(&requests, allLogs, config); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse deposit logs: %w", err)
|
||||
}
|
||||
// EIP-7002
|
||||
|
|
@ -123,7 +127,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
|
||||
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
|
||||
p.chain.engine.Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body())
|
||||
|
||||
return &ProcessResult{
|
||||
Receipts: receipts,
|
||||
|
|
|
|||
|
|
@ -164,8 +164,12 @@ type Message struct {
|
|||
// or the state prefetching.
|
||||
SkipNonceChecks bool
|
||||
|
||||
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
|
||||
SkipFromEOACheck bool
|
||||
// When set, the message is not treated as a transaction, and certain
|
||||
// transaction-specific checks are skipped:
|
||||
//
|
||||
// - From is not verified to be an EOA
|
||||
// - GasLimit is not checked against the protocol defined tx gaslimit
|
||||
SkipTransactionChecks bool
|
||||
}
|
||||
|
||||
// TransactionToMessage converts a transaction into a Message.
|
||||
|
|
@ -182,7 +186,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
|||
AccessList: tx.AccessList(),
|
||||
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
|
||||
SkipNonceChecks: false,
|
||||
SkipFromEOACheck: false,
|
||||
SkipTransactionChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
}
|
||||
|
|
@ -320,7 +324,12 @@ func (st *stateTransition) preCheck() error {
|
|||
msg.From.Hex(), stNonce)
|
||||
}
|
||||
}
|
||||
if !msg.SkipFromEOACheck {
|
||||
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
if !msg.SkipTransactionChecks {
|
||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||
if isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
}
|
||||
// Make sure the sender is an EOA
|
||||
code := st.state.GetCode(msg.From)
|
||||
_, delegated := types.ParseDelegation(code)
|
||||
|
|
@ -354,7 +363,6 @@ func (st *stateTransition) preCheck() error {
|
|||
}
|
||||
}
|
||||
// Check the blob version validity
|
||||
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
if msg.BlobHashes != nil {
|
||||
// The to field of a blob tx type is mandatory, and a `BlobTx` transaction internally
|
||||
// has it as a non-nillable value, so any msg derived from blob transaction has it non-nil.
|
||||
|
|
@ -398,10 +406,6 @@ func (st *stateTransition) preCheck() error {
|
|||
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
||||
}
|
||||
}
|
||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||
if isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
}
|
||||
return st.buyGas()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ
|
|||
headerCache: lru.NewCache[common.Hash, *types.Header](256),
|
||||
engine: beacon.New(ethash.NewFaker()),
|
||||
}
|
||||
processor := NewStateProcessor(config, chain)
|
||||
processor := NewStateProcessor(chain)
|
||||
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
||||
|
||||
// Run the stateless blocks processing and self-validate certain fields
|
||||
|
|
|
|||
|
|
@ -21,12 +21,15 @@ import (
|
|||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -61,11 +64,11 @@ const (
|
|||
// small buffer is added to the proof overhead.
|
||||
txBlobOverhead = uint32(kzg4844.CellProofsPerBlob*len(kzg4844.Proof{}) + 64)
|
||||
|
||||
// txMaxSize is the maximum size a single transaction can have, outside
|
||||
// the included blobs. Since blob transactions are pulled instead of pushed,
|
||||
// and only a small metadata is kept in ram, the rest is on disk, there is
|
||||
// no critical limit that should be enforced. Still, capping it to some sane
|
||||
// limit can never hurt.
|
||||
// txMaxSize is the maximum size a single transaction can have, including the
|
||||
// blobs. Since blob transactions are pulled instead of pushed, and only a
|
||||
// small metadata is kept in ram, the rest is on disk, there is no critical
|
||||
// limit that should be enforced. Still, capping it to some sane limit can
|
||||
// never hurt, which is aligned with maxBlobsPerTx constraint enforced internally.
|
||||
txMaxSize = 1024 * 1024
|
||||
|
||||
// maxBlobsPerTx is the maximum number of blobs that a single transaction can
|
||||
|
|
@ -93,6 +96,11 @@ const (
|
|||
// storeVersion is the current slotter layout used for the billy.Database
|
||||
// store.
|
||||
storeVersion = 1
|
||||
|
||||
// conversionTimeWindow defines the period after the Osaka fork during which
|
||||
// the pool will still accept and convert legacy blob transactions. After this
|
||||
// window, all legacy blob transactions will be rejected.
|
||||
conversionTimeWindow = time.Hour * 2
|
||||
)
|
||||
|
||||
// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
|
||||
|
|
@ -396,12 +404,13 @@ type BlobPool struct {
|
|||
stored uint64 // Useful data size of all transactions on disk
|
||||
limbo *limbo // Persistent data store for the non-finalized blobs
|
||||
|
||||
signer types.Signer // Transaction signer to use for sender recovery
|
||||
chain BlockChain // Chain object to access the state through
|
||||
signer types.Signer // Transaction signer to use for sender recovery
|
||||
chain BlockChain // Chain object to access the state through
|
||||
cQueue *conversionQueue // The queue for performing legacy sidecar conversion (TODO: remove after Osaka)
|
||||
|
||||
head *types.Header // Current head of the chain
|
||||
state *state.StateDB // Current state at the head of the chain
|
||||
gasTip *uint256.Int // Currently accepted minimum gas tip
|
||||
head atomic.Pointer[types.Header] // Current head of the chain
|
||||
state *state.StateDB // Current state at the head of the chain
|
||||
gasTip atomic.Pointer[uint256.Int] // Currently accepted minimum gas tip
|
||||
|
||||
lookup *lookup // Lookup table mapping blobs to txs and txs to billy entries
|
||||
index map[common.Address][]*blobTxMeta // Blob transactions grouped by accounts, sorted by nonce
|
||||
|
|
@ -426,6 +435,7 @@ func New(config Config, chain BlockChain, hasPendingAuth func(common.Address) bo
|
|||
hasPendingAuth: hasPendingAuth,
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
chain: chain,
|
||||
cQueue: newConversionQueue(), // Deprecate it after the osaka fork
|
||||
lookup: newLookup(),
|
||||
index: make(map[common.Address][]*blobTxMeta),
|
||||
spent: make(map[common.Address]*uint256.Int),
|
||||
|
|
@ -467,7 +477,8 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.head, p.state = head, state
|
||||
p.head.Store(head)
|
||||
p.state = state
|
||||
|
||||
// Create new slotter for pre-Osaka blob configuration.
|
||||
slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(p.chain.Config()))
|
||||
|
|
@ -545,11 +556,11 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
p.recheck(addr, nil)
|
||||
}
|
||||
var (
|
||||
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head))
|
||||
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), head))
|
||||
blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice)
|
||||
)
|
||||
if p.head.ExcessBlobGas != nil {
|
||||
blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(p.chain.Config(), p.head))
|
||||
if head.ExcessBlobGas != nil {
|
||||
blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(p.chain.Config(), head))
|
||||
}
|
||||
p.evict = newPriceHeap(basefee, blobfee, p.index)
|
||||
|
||||
|
|
@ -579,6 +590,9 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserver txpool.Reser
|
|||
|
||||
// Close closes down the underlying persistent store.
|
||||
func (p *BlobPool) Close() error {
|
||||
// Terminate the conversion queue
|
||||
p.cQueue.close()
|
||||
|
||||
var errs []error
|
||||
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 {
|
||||
|
|
@ -952,7 +966,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
|||
log.Error("Failed to reset blobpool state", "err", err)
|
||||
return
|
||||
}
|
||||
p.head = newHead
|
||||
p.head.Store(newHead)
|
||||
p.state = statedb
|
||||
|
||||
// Run the reorg between the old and new head and figure out which accounts
|
||||
|
|
@ -975,7 +989,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
|||
}
|
||||
}
|
||||
// Flush out any blobs from limbo that are older than the latest finality
|
||||
if p.chain.Config().IsCancun(p.head.Number, p.head.Time) {
|
||||
if p.chain.Config().IsCancun(newHead.Number, newHead.Time) {
|
||||
p.limbo.finalize(p.chain.CurrentFinalBlock())
|
||||
}
|
||||
// Reset the price heap for the new set of basefee/blobfee pairs
|
||||
|
|
@ -991,6 +1005,172 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
|
|||
basefeeGauge.Update(int64(basefee.Uint64()))
|
||||
blobfeeGauge.Update(int64(blobfee.Uint64()))
|
||||
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
|
||||
|
|
@ -1130,7 +1310,7 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*
|
|||
func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
||||
// Retrieve the associated blob from the limbo. Without the blobs, we cannot
|
||||
// add the transaction back into the pool as it is not mineable.
|
||||
tx, err := p.limbo.pull(txhash)
|
||||
pooledTx, err := p.limbo.pull(txhash)
|
||||
if err != nil {
|
||||
log.Error("Blobs unavailable, dropping reorged tx", "err", err)
|
||||
return err
|
||||
|
|
@ -1138,23 +1318,43 @@ func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error {
|
|||
// TODO: seems like an easy optimization here would be getting the serialized tx
|
||||
// from limbo instead of re-serializing it here.
|
||||
|
||||
// Converts reorged-out legacy blob transactions to the new format to prevent
|
||||
// them from becoming stuck in the pool until eviction.
|
||||
//
|
||||
// Performance note: Conversion takes ~140ms (Mac M1 Pro). Since a maximum of
|
||||
// 9 legacy blob transactions are allowed in a block pre-Osaka, an adversary
|
||||
// could theoretically halt a Geth node for ~1.2s by reorging per block. However,
|
||||
// this attack is financially inefficient to execute.
|
||||
head := p.head.Load()
|
||||
if p.chain.Config().IsOsaka(head.Number, head.Time) && pooledTx.Sidecar.Version == types.BlobSidecarVersion0 {
|
||||
tx, err := pooledTx.convert()
|
||||
if err != nil {
|
||||
log.Error("Failed to convert the pooledTx", "err", err)
|
||||
return err
|
||||
}
|
||||
if err := tx.BlobTxSidecar().ToV1(); err != nil {
|
||||
log.Error("Failed to convert the legacy sidecar", "err", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Legacy blob transaction is reorged", "hash", tx.Hash())
|
||||
}
|
||||
// Serialize the transaction back into the primary datastore.
|
||||
blob, err := rlp.EncodeToBytes(tx)
|
||||
blob, err := rlp.EncodeToBytes(pooledTx)
|
||||
if err != nil {
|
||||
log.Error("Failed to encode transaction for storage", "hash", tx.Transaction.Hash(), "err", err)
|
||||
log.Error("Failed to encode transaction for storage", "hash", pooledTx.Transaction.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
id, err := p.store.Put(blob)
|
||||
if err != nil {
|
||||
log.Error("Failed to write transaction into storage", "hash", tx.Transaction.Hash(), "err", err)
|
||||
log.Error("Failed to write transaction into storage", "hash", pooledTx.Transaction.Hash(), "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the indices and metrics
|
||||
meta := newBlobTxMeta(id, tx.Size, p.store.Size(id), tx)
|
||||
meta := newBlobTxMeta(id, pooledTx.Size, p.store.Size(id), pooledTx)
|
||||
if _, ok := p.index[addr]; !ok {
|
||||
if err := p.reserver.Hold(addr); err != nil {
|
||||
log.Warn("Failed to reserve account for blob pool", "tx", tx.Transaction.Hash(), "from", addr, "err", err)
|
||||
log.Warn("Failed to reserve account for blob pool", "tx", pooledTx.Transaction.Hash(), "from", addr, "err", err)
|
||||
return err
|
||||
}
|
||||
p.index[addr] = []*blobTxMeta{meta}
|
||||
|
|
@ -1176,14 +1376,15 @@ func (p *BlobPool) SetGasTip(tip *big.Int) {
|
|||
defer p.lock.Unlock()
|
||||
|
||||
// Store the new minimum gas tip
|
||||
old := p.gasTip
|
||||
p.gasTip = uint256.MustFromBig(tip)
|
||||
old := p.gasTip.Load()
|
||||
newTip := uint256.MustFromBig(tip)
|
||||
p.gasTip.Store(newTip)
|
||||
|
||||
// If the min miner fee increased, remove transactions below the new threshold
|
||||
if old == nil || p.gasTip.Cmp(old) > 0 {
|
||||
if old == nil || newTip.Cmp(old) > 0 {
|
||||
for addr, txs := range p.index {
|
||||
for i, tx := range txs {
|
||||
if tx.execTipCap.Cmp(p.gasTip) < 0 {
|
||||
if tx.execTipCap.Cmp(newTip) < 0 {
|
||||
// Drop the offending transaction
|
||||
var (
|
||||
ids = []uint64{tx.id}
|
||||
|
|
@ -1243,10 +1444,10 @@ func (p *BlobPool) ValidateTxBasics(tx *types.Transaction) error {
|
|||
Config: p.chain.Config(),
|
||||
Accept: 1 << types.BlobTxType,
|
||||
MaxSize: txMaxSize,
|
||||
MinTip: p.gasTip.ToBig(),
|
||||
MinTip: p.gasTip.Load().ToBig(),
|
||||
MaxBlobCount: maxBlobsPerTx,
|
||||
}
|
||||
return txpool.ValidateTransaction(tx, p.head, p.signer, opts)
|
||||
return txpool.ValidateTransaction(tx, p.head.Load(), p.signer, opts)
|
||||
}
|
||||
|
||||
// checkDelegationLimit determines if the tx sender is delegated or has a
|
||||
|
|
@ -1284,8 +1485,11 @@ func (p *BlobPool) checkDelegationLimit(tx *types.Transaction) error {
|
|||
|
||||
// validateTx checks whether a transaction is valid according to the consensus
|
||||
// rules and adheres to some heuristic limits of the local node (price and size).
|
||||
//
|
||||
// This function assumes the static validation has been performed already and
|
||||
// only runs the stateful checks with lock protection.
|
||||
func (p *BlobPool) validateTx(pooledTx *pooledBlobTx) error {
|
||||
if err := txpool.ValidateBlobSidecar(pooledTx.Transaction, pooledTx.Sidecar, p.head, &txpool.ValidationOptions{
|
||||
if err := txpool.ValidateBlobSidecar(pooledTx.Transaction, pooledTx.Sidecar, &txpool.ValidationOptions{
|
||||
Config: p.chain.Config(),
|
||||
MaxBlobCount: maxBlobsPerTx,
|
||||
}); err != nil {
|
||||
|
|
@ -1293,9 +1497,6 @@ func (p *BlobPool) validateTx(pooledTx *pooledBlobTx) error {
|
|||
}
|
||||
|
||||
tx := pooledTx.Transaction
|
||||
if err := p.ValidateTxBasics(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
// Ensure the transaction adheres to the stateful pool filters (nonce, balance)
|
||||
stateOpts := &txpool.ValidationOptionsWithState{
|
||||
State: p.state,
|
||||
|
|
@ -1482,7 +1683,11 @@ func (p *BlobPool) GetMetadata(hash common.Hash) *txpool.TxMetadata {
|
|||
//
|
||||
// This is a utility method for the engine API, enabling consensus clients to
|
||||
// retrieve blobs from the pools directly instead of the network.
|
||||
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
|
||||
//
|
||||
// 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
|
||||
// CPU intensive, so only done if explicitly requested with the convert flag.
|
||||
func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte, convert bool) ([]*kzg4844.Blob, []kzg4844.Commitment, [][]kzg4844.Proof, error) {
|
||||
var (
|
||||
blobs = make([]*kzg4844.Blob, len(vhashes))
|
||||
commitments = make([]kzg4844.Commitment, len(vhashes))
|
||||
|
|
@ -1494,13 +1699,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
for i, h := range vhashes {
|
||||
indices[h] = append(indices[h], i)
|
||||
}
|
||||
|
||||
for _, vhash := range vhashes {
|
||||
// Skip duplicate vhash that was already resolved in a previous iteration
|
||||
if _, ok := filled[vhash]; ok {
|
||||
// Skip vhash that was already resolved in a previous iteration
|
||||
continue
|
||||
}
|
||||
// Retrieve the corresponding blob tx with the vhash, skip blob resolution
|
||||
// if it's not found locally and place the null instead.
|
||||
|
||||
// Retrieve the corresponding blob tx with the vhash.
|
||||
p.lock.RLock()
|
||||
txID, exists := p.lookup.storeidOfBlob(vhash)
|
||||
p.lock.RUnlock()
|
||||
|
|
@ -1536,6 +1742,14 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
if !ok {
|
||||
continue // non-interesting blob
|
||||
}
|
||||
// Mark hash as seen.
|
||||
filled[hash] = struct{}{}
|
||||
if sidecar.Version != version && !convert {
|
||||
// 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.
|
||||
continue
|
||||
}
|
||||
// Get or convert the proof.
|
||||
var pf []kzg4844.Proof
|
||||
switch version {
|
||||
case types.BlobSidecarVersion0:
|
||||
|
|
@ -1568,7 +1782,6 @@ func (p *BlobPool) GetBlobs(vhashes []common.Hash, version byte) ([]*kzg4844.Blo
|
|||
commitments[index] = sidecar.Commitments[i]
|
||||
proofs[index] = pf
|
||||
}
|
||||
filled[hash] = struct{}{}
|
||||
}
|
||||
}
|
||||
return blobs, commitments, proofs, nil
|
||||
|
|
@ -1589,23 +1802,60 @@ func (p *BlobPool) AvailableBlobs(vhashes []common.Hash) int {
|
|||
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
|
||||
// consensus validity and pool restrictions).
|
||||
//
|
||||
// Note, if sync is set the method will block until all internal maintenance
|
||||
// related to the add is finished. Only use this during tests for determinism.
|
||||
func (p *BlobPool) Add(txs []*types.Transaction, sync bool) []error {
|
||||
var (
|
||||
errs = make([]error, len(txs))
|
||||
adds = make([]*types.Transaction, 0, len(txs))
|
||||
errs []error = make([]error, len(txs))
|
||||
adds = make([]*types.Transaction, 0, len(txs))
|
||||
)
|
||||
for i, tx := range txs {
|
||||
if errs[i] != nil {
|
||||
if errs[i] = p.preCheck(tx); errs[i] != nil {
|
||||
continue
|
||||
}
|
||||
pooledTx, err := newPooledBlobTx(tx)
|
||||
if err != nil {
|
||||
errs[i] = err
|
||||
continue
|
||||
}
|
||||
errs[i] = p.add(pooledTx)
|
||||
|
|
@ -2106,7 +2356,7 @@ func (p *BlobPool) Clear() {
|
|||
p.spent = make(map[common.Address]*uint256.Int)
|
||||
|
||||
var (
|
||||
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head))
|
||||
basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head.Load()))
|
||||
blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice)
|
||||
)
|
||||
p.evict = newPriceHeap(basefee, blobfee, p.index)
|
||||
|
|
|
|||
|
|
@ -88,6 +88,12 @@ type testBlockChain struct {
|
|||
statedb *state.StateDB
|
||||
|
||||
blocks map[uint64]*types.Block
|
||||
|
||||
blockTime *uint64
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) setHeadTime(time uint64) {
|
||||
bc.blockTime = &time
|
||||
}
|
||||
|
||||
func (bc *testBlockChain) Config() *params.ChainConfig {
|
||||
|
|
@ -105,6 +111,10 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
|
|||
blockTime = *bc.config.CancunTime + 1
|
||||
gasLimit = uint64(30_000_000)
|
||||
)
|
||||
if bc.blockTime != nil {
|
||||
blockTime = *bc.blockTime
|
||||
}
|
||||
|
||||
lo := new(big.Int)
|
||||
hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
|
||||
|
||||
|
|
@ -423,11 +433,11 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
|||
hashes = append(hashes, tx.vhashes...)
|
||||
}
|
||||
}
|
||||
blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0)
|
||||
blobs1, _, proofs1, err := pool.GetBlobs(hashes, types.BlobSidecarVersion0, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1)
|
||||
blobs2, _, proofs2, err := pool.GetBlobs(hashes, types.BlobSidecarVersion1, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -441,26 +451,18 @@ func verifyBlobRetrievals(t *testing.T, pool *BlobPool) {
|
|||
return
|
||||
}
|
||||
for i, hash := range hashes {
|
||||
// If an item is missing, but shouldn't, error
|
||||
if blobs1[i] == nil || proofs1[i] == nil {
|
||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash)
|
||||
continue
|
||||
}
|
||||
if blobs2[i] == nil || proofs2[i] == nil {
|
||||
// If an item is missing from both, but shouldn't, error
|
||||
if (blobs1[i] == nil || proofs1[i] == nil) && (blobs2[i] == nil || proofs2[i] == nil) {
|
||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash)
|
||||
continue
|
||||
}
|
||||
// Item retrieved, make sure it matches the expectation
|
||||
index := testBlobIndices[hash]
|
||||
if *blobs1[i] != *testBlobs[index] {
|
||||
t.Errorf("retrieved blob mismatch: item %d, hash %x", i, hash)
|
||||
if blobs1[i] != nil && (*blobs1[i] != *testBlobs[index] || proofs1[i][0] != testBlobProofs[index]) {
|
||||
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
|
||||
continue
|
||||
}
|
||||
if proofs1[i][0] != testBlobProofs[index] {
|
||||
t.Errorf("retrieved proof mismatch: item %d, hash %x", i, hash)
|
||||
continue
|
||||
}
|
||||
if *blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index]) {
|
||||
if blobs2[i] != nil && (*blobs2[i] != *testBlobs[index] || !slices.Equal(proofs2[i], testBlobCellProofs[index])) {
|
||||
t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash)
|
||||
continue
|
||||
}
|
||||
|
|
@ -1762,10 +1764,8 @@ func TestAdd(t *testing.T) {
|
|||
// Add each transaction one by one, verifying the pool internals in between
|
||||
for j, add := range tt.adds {
|
||||
signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx)
|
||||
pooledTx, _ := newPooledBlobTx(signed)
|
||||
|
||||
if err := pool.add(pooledTx); !errors.Is(err, add.err) {
|
||||
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
|
||||
if errs := pool.Add([]*types.Transaction{signed}, true); !errors.Is(errs[0], add.err) {
|
||||
t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, errs[0], add.err)
|
||||
}
|
||||
if add.err == nil {
|
||||
size, exist := pool.lookup.sizeOfTx(signed.Hash())
|
||||
|
|
@ -1812,8 +1812,14 @@ func TestAdd(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Tests adding transactions with legacy sidecars are correctly rejected.
|
||||
// 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()
|
||||
|
|
@ -1833,6 +1839,15 @@ func TestAddLegacyBlobTx(t *testing.T) {
|
|||
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)
|
||||
|
|
@ -1842,12 +1857,15 @@ func TestAddLegacyBlobTx(t *testing.T) {
|
|||
var (
|
||||
tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, 0, key1, types.BlobSidecarVersion0)
|
||||
tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, 6, key2, types.BlobSidecarVersion0)
|
||||
tx3 = makeMultiBlobTx(1, 1, 800, 70, 6, 12, key2, types.BlobSidecarVersion1)
|
||||
txs = []*types.Transaction{tx1, tx2}
|
||||
)
|
||||
errs := pool.Add([]*types.Transaction{tx1, tx2, tx3}, true)
|
||||
errs := pool.Add(txs, true)
|
||||
for _, err := range errs {
|
||||
if err == nil {
|
||||
t.Fatalf("expected tx add to fail")
|
||||
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)
|
||||
|
|
@ -1938,8 +1956,9 @@ func TestGetBlobs(t *testing.T) {
|
|||
cases := []struct {
|
||||
start int
|
||||
limit int
|
||||
fillRandom bool
|
||||
version byte
|
||||
fillRandom bool // Whether to randomly fill some of the requested blobs with unknowns
|
||||
version byte // Blob sidecar version to request
|
||||
convert bool // Whether to convert version on retrieval
|
||||
}{
|
||||
{
|
||||
start: 0, limit: 6,
|
||||
|
|
@ -2005,6 +2024,11 @@ func TestGetBlobs(t *testing.T) {
|
|||
start: 0, limit: 18, fillRandom: true,
|
||||
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 {
|
||||
var (
|
||||
|
|
@ -2026,7 +2050,7 @@ func TestGetBlobs(t *testing.T) {
|
|||
filled[len(vhashes)] = struct{}{}
|
||||
vhashes = append(vhashes, testrand.Hash())
|
||||
}
|
||||
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version)
|
||||
blobs, _, proofs, err := pool.GetBlobs(vhashes, c.version, c.convert)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for case %d, %v", i, err)
|
||||
}
|
||||
|
|
@ -2041,6 +2065,7 @@ func TestGetBlobs(t *testing.T) {
|
|||
|
||||
var unknown int
|
||||
for j := 0; j < len(blobs); j++ {
|
||||
testBlobIndex := c.start + j - unknown
|
||||
if _, exist := filled[j]; exist {
|
||||
if blobs[j] != nil || proofs[j] != nil {
|
||||
t.Errorf("Unexpected blob and proof, item %d", j)
|
||||
|
|
@ -2050,17 +2075,22 @@ func TestGetBlobs(t *testing.T) {
|
|||
}
|
||||
// If an item is missing, but shouldn't, error
|
||||
if blobs[j] == nil || proofs[j] == nil {
|
||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
|
||||
// This is only an error if there was no version mismatch
|
||||
if c.convert ||
|
||||
(c.version == types.BlobSidecarVersion1 && 6 <= testBlobIndex && testBlobIndex < 12) ||
|
||||
(c.version == types.BlobSidecarVersion0 && (testBlobIndex < 6 || 12 <= testBlobIndex)) {
|
||||
t.Errorf("tracked blob retrieval failed: item %d, hash %x", j, vhashes[j])
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Item retrieved, make sure the blob matches the expectation
|
||||
if *blobs[j] != *testBlobs[c.start+j-unknown] {
|
||||
if *blobs[j] != *testBlobs[testBlobIndex] {
|
||||
t.Errorf("retrieved blob mismatch: item %d, hash %x", j, vhashes[j])
|
||||
continue
|
||||
}
|
||||
// Item retrieved, make sure the proof matches the expectation
|
||||
if c.version == types.BlobSidecarVersion0 {
|
||||
if proofs[j][0] != testBlobProofs[c.start+j-unknown] {
|
||||
if proofs[j][0] != testBlobProofs[testBlobIndex] {
|
||||
t.Errorf("retrieved proof mismatch: item %d, hash %x", j, vhashes[j])
|
||||
}
|
||||
} else {
|
||||
|
|
@ -2074,6 +2104,185 @@ func TestGetBlobs(t *testing.T) {
|
|||
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.
|
||||
type fakeBilly struct {
|
||||
billy.Database
|
||||
|
|
@ -2157,3 +2366,5 @@ func benchmarkPoolPending(b *testing.B, datacap uint64) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newUint64(val uint64) *uint64 { return &val }
|
||||
|
|
|
|||
203
core/txpool/blobpool/conversion.go
Normal file
203
core/txpool/blobpool/conversion.go
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
// 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
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
101
core/txpool/blobpool/conversion_test.go
Normal file
101
core/txpool/blobpool/conversion_test.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// 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"
|
||||
"testing"
|
||||
|
||||
"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
|
||||
}
|
||||
|
|
@ -110,3 +110,13 @@ 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"math"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
|
@ -238,11 +237,10 @@ type LegacyPool struct {
|
|||
pendingNonces *noncer // Pending state tracking virtual nonces
|
||||
reserver txpool.Reserver // Address reserver to ensure exclusivity across subpools
|
||||
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue map[common.Address]*list // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
pending map[common.Address]*list // All currently processable transactions
|
||||
queue *queue
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
|
||||
reqResetCh chan *txpoolResetRequest
|
||||
reqPromoteCh chan *accountSet
|
||||
|
|
@ -266,14 +264,14 @@ func New(config Config, chain BlockChain) *LegacyPool {
|
|||
config = (&config).sanitize()
|
||||
|
||||
// Create the transaction pool with its initial settings
|
||||
signer := types.LatestSigner(chain.Config())
|
||||
pool := &LegacyPool{
|
||||
config: config,
|
||||
chain: chain,
|
||||
chainconfig: chain.Config(),
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
signer: signer,
|
||||
pending: make(map[common.Address]*list),
|
||||
queue: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
queue: newQueue(config, signer),
|
||||
all: newLookup(),
|
||||
reqResetCh: make(chan *txpoolResetRequest),
|
||||
reqPromoteCh: make(chan *accountSet),
|
||||
|
|
@ -369,15 +367,8 @@ func (pool *LegacyPool) loop() {
|
|||
// Handle inactive account transaction eviction
|
||||
case <-evict.C:
|
||||
pool.mu.Lock()
|
||||
for addr := range pool.queue {
|
||||
// Any old enough should be removed
|
||||
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
|
||||
list := pool.queue[addr].Flatten()
|
||||
for _, tx := range list {
|
||||
pool.removeTx(tx.Hash(), true, true)
|
||||
}
|
||||
queuedEvictionMeter.Mark(int64(len(list)))
|
||||
}
|
||||
for _, hash := range pool.queue.evictList() {
|
||||
pool.removeTx(hash, true, true)
|
||||
}
|
||||
pool.mu.Unlock()
|
||||
}
|
||||
|
|
@ -459,11 +450,7 @@ func (pool *LegacyPool) stats() (int, int) {
|
|||
for _, list := range pool.pending {
|
||||
pending += list.Len()
|
||||
}
|
||||
queued := 0
|
||||
for _, list := range pool.queue {
|
||||
queued += list.Len()
|
||||
}
|
||||
return pending, queued
|
||||
return pending, pool.queue.stats()
|
||||
}
|
||||
|
||||
// Content retrieves the data content of the transaction pool, returning all the
|
||||
|
|
@ -476,10 +463,7 @@ func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[
|
|||
for addr, list := range pool.pending {
|
||||
pending[addr] = list.Flatten()
|
||||
}
|
||||
queued := make(map[common.Address][]*types.Transaction, len(pool.queue))
|
||||
for addr, list := range pool.queue {
|
||||
queued[addr] = list.Flatten()
|
||||
}
|
||||
queued := pool.queue.content()
|
||||
return pending, queued
|
||||
}
|
||||
|
||||
|
|
@ -493,10 +477,7 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
|
|||
if list, ok := pool.pending[addr]; ok {
|
||||
pending = list.Flatten()
|
||||
}
|
||||
var queued []*types.Transaction
|
||||
if list, ok := pool.queue[addr]; ok {
|
||||
queued = list.Flatten()
|
||||
}
|
||||
queued := pool.queue.contentFrom(addr)
|
||||
return pending, queued
|
||||
}
|
||||
|
||||
|
|
@ -644,7 +625,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
|
|||
if pending := pool.pending[auth]; pending != nil {
|
||||
count += pending.Len()
|
||||
}
|
||||
if queue := pool.queue[auth]; queue != nil {
|
||||
if queue, ok := pool.queue.get(auth); ok {
|
||||
count += queue.Len()
|
||||
}
|
||||
if count > 1 {
|
||||
|
|
@ -691,7 +672,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
|||
// only by this subpool until all transactions are evicted
|
||||
var (
|
||||
_, hasPending = pool.pending[from]
|
||||
_, hasQueued = pool.queue[from]
|
||||
_, hasQueued = pool.queue.get(from)
|
||||
)
|
||||
if !hasPending && !hasQueued {
|
||||
if err := pool.reserver.Hold(from); err != nil {
|
||||
|
|
@ -790,7 +771,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
|||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[from] = time.Now()
|
||||
pool.queue.bump(from)
|
||||
return old != nil, nil
|
||||
}
|
||||
// New transaction isn't replacing a pending one, push into queue
|
||||
|
|
@ -815,7 +796,7 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
|
|||
}
|
||||
// The transaction has a nonce gap with pending list, it's only considered
|
||||
// as executable if transactions in queue can fill up the nonce gap.
|
||||
queue, ok := pool.queue[from]
|
||||
queue, ok := pool.queue.get(from)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
|
@ -831,25 +812,12 @@ func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) boo
|
|||
//
|
||||
// Note, this method assumes the pool lock is held!
|
||||
func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAll bool) (bool, error) {
|
||||
// Try to insert the transaction into the future queue
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
if pool.queue[from] == nil {
|
||||
pool.queue[from] = newList(false)
|
||||
replaced, err := pool.queue.add(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
|
||||
if !inserted {
|
||||
// An older transaction was better, discard this
|
||||
queuedDiscardMeter.Mark(1)
|
||||
return false, txpool.ErrReplaceUnderpriced
|
||||
}
|
||||
// Discard any previous transaction and mark this
|
||||
if old != nil {
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed(1)
|
||||
queuedReplaceMeter.Mark(1)
|
||||
} else {
|
||||
// Nothing was replaced, bump the queued counter
|
||||
queuedGauge.Inc(1)
|
||||
if replaced != nil {
|
||||
pool.removeTx(*replaced, true, true)
|
||||
}
|
||||
// If the transaction isn't in lookup set but it's expected to be there,
|
||||
// show the error log.
|
||||
|
|
@ -860,11 +828,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
|
|||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
// If we never record the heartbeat, do it right now.
|
||||
if _, exist := pool.beats[from]; !exist {
|
||||
pool.beats[from] = time.Now()
|
||||
}
|
||||
return old != nil, nil
|
||||
return replaced != nil, nil
|
||||
}
|
||||
|
||||
// promoteTx adds a transaction to the pending (processable) list of transactions
|
||||
|
|
@ -899,7 +863,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
|
|||
pool.pendingNonces.set(addr, tx.Nonce()+1)
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
pool.beats[addr] = time.Now()
|
||||
pool.queue.bump(addr)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -984,17 +948,24 @@ func (pool *LegacyPool) Add(txs []*types.Transaction, sync bool) []error {
|
|||
|
||||
// addTxsLocked attempts to queue a batch of transactions if they are valid.
|
||||
// The transaction pool lock must be held.
|
||||
// Returns the error for each tx, and the set of accounts that might became promotable.
|
||||
func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction) ([]error, *accountSet) {
|
||||
dirty := newAccountSet(pool.signer)
|
||||
errs := make([]error, len(txs))
|
||||
var (
|
||||
dirty = newAccountSet(pool.signer)
|
||||
errs = make([]error, len(txs))
|
||||
valid int64
|
||||
)
|
||||
for i, tx := range txs {
|
||||
replaced, err := pool.add(tx)
|
||||
errs[i] = err
|
||||
if err == nil && !replaced {
|
||||
dirty.addTx(tx)
|
||||
if err == nil {
|
||||
if !replaced {
|
||||
dirty.addTx(tx)
|
||||
}
|
||||
valid++
|
||||
}
|
||||
}
|
||||
validTxMeter.Mark(int64(len(dirty.accounts)))
|
||||
validTxMeter.Mark(valid)
|
||||
return errs, dirty
|
||||
}
|
||||
|
||||
|
|
@ -1012,7 +983,7 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus {
|
|||
|
||||
if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
||||
return txpool.TxStatusPending
|
||||
} else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil {
|
||||
} else if txList, ok := pool.queue.get(from); ok && txList.txs.items[tx.Nonce()] != nil {
|
||||
return txpool.TxStatusQueued
|
||||
}
|
||||
return txpool.TxStatusUnknown
|
||||
|
|
@ -1089,7 +1060,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
defer func() {
|
||||
var (
|
||||
_, hasPending = pool.pending[addr]
|
||||
_, hasQueued = pool.queue[addr]
|
||||
_, hasQueued = pool.queue.get(addr)
|
||||
)
|
||||
if !hasPending && !hasQueued {
|
||||
pool.reserver.Release(addr)
|
||||
|
|
@ -1121,16 +1092,7 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
}
|
||||
}
|
||||
// Transaction is in the future queue
|
||||
if future := pool.queue[addr]; future != nil {
|
||||
if removed, _ := future.Remove(tx); removed {
|
||||
// Reduce the queued counter
|
||||
queuedGauge.Dec(1)
|
||||
}
|
||||
if future.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
}
|
||||
}
|
||||
pool.queue.remove(addr, tx)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -1278,10 +1240,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
|
|||
}
|
||||
}
|
||||
// Reset needs promote for all addresses
|
||||
promoteAddrs = make([]common.Address, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
promoteAddrs = append(promoteAddrs, addr)
|
||||
}
|
||||
promoteAddrs = pool.queue.addresses()
|
||||
}
|
||||
// Check for pending transactions for every account that sent new ones
|
||||
promoted := pool.promoteExecutables(promoteAddrs)
|
||||
|
|
@ -1435,60 +1394,29 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
|
|||
// future queue to the set of pending transactions. During this process, all
|
||||
// invalidated transactions (low nonce, low balance) are deleted.
|
||||
func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction {
|
||||
// Track the promoted transactions to broadcast them at once
|
||||
var promoted []*types.Transaction
|
||||
|
||||
// Iterate over all accounts and promote any executable transactions
|
||||
gasLimit := pool.currentHead.Load().GasLimit
|
||||
for _, addr := range accounts {
|
||||
list := pool.queue[addr]
|
||||
if list == nil {
|
||||
continue // Just in case someone calls with a non existing account
|
||||
}
|
||||
// Drop all transactions that are deemed too old (low nonce)
|
||||
forwards := list.Forward(pool.currentState.GetNonce(addr))
|
||||
for _, tx := range forwards {
|
||||
pool.all.Remove(tx.Hash())
|
||||
}
|
||||
log.Trace("Removed old queued transactions", "count", len(forwards))
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
|
||||
for _, tx := range drops {
|
||||
pool.all.Remove(tx.Hash())
|
||||
}
|
||||
log.Trace("Removed unpayable queued transactions", "count", len(drops))
|
||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
||||
promotable, dropped, removedAddresses := pool.queue.promoteExecutables(accounts, gasLimit, pool.currentState, pool.pendingNonces)
|
||||
|
||||
// Gather all executable transactions and promote them
|
||||
readies := list.Ready(pool.pendingNonces.get(addr))
|
||||
for _, tx := range readies {
|
||||
hash := tx.Hash()
|
||||
if pool.promoteTx(addr, hash, tx) {
|
||||
promoted = append(promoted, tx)
|
||||
}
|
||||
// promote all promotable transactions
|
||||
promoted := make([]*types.Transaction, 0, len(promotable))
|
||||
for _, tx := range promotable {
|
||||
from, _ := pool.signer.Sender(tx)
|
||||
if pool.promoteTx(from, tx.Hash(), tx) {
|
||||
promoted = append(promoted, tx)
|
||||
}
|
||||
log.Trace("Promoted queued transactions", "count", len(promoted))
|
||||
queuedGauge.Dec(int64(len(readies)))
|
||||
}
|
||||
|
||||
// Drop all transactions over the allowed limit
|
||||
var caps = list.Cap(int(pool.config.AccountQueue))
|
||||
for _, tx := range caps {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
|
||||
}
|
||||
queuedRateLimitMeter.Mark(int64(len(caps)))
|
||||
// Mark all the items dropped as removed
|
||||
pool.priced.Removed(len(forwards) + len(drops) + len(caps))
|
||||
queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps)))
|
||||
// remove all removable transactions
|
||||
for _, hash := range dropped {
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(dropped))
|
||||
|
||||
// Delete the entire queue entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.queue, addr)
|
||||
delete(pool.beats, addr)
|
||||
if _, ok := pool.pending[addr]; !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
// release all accounts that have no more transactions in the pool
|
||||
for _, addr := range removedAddresses {
|
||||
_, hasPending := pool.pending[addr]
|
||||
if !hasPending {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
return promoted
|
||||
|
|
@ -1578,43 +1506,18 @@ func (pool *LegacyPool) truncatePending() {
|
|||
|
||||
// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit.
|
||||
func (pool *LegacyPool) truncateQueue() {
|
||||
queued := uint64(0)
|
||||
for _, list := range pool.queue {
|
||||
queued += uint64(list.Len())
|
||||
}
|
||||
if queued <= pool.config.GlobalQueue {
|
||||
return
|
||||
removed, removedAddresses := pool.queue.truncate()
|
||||
|
||||
// Remove all removable transactions from the lookup and global price list
|
||||
for _, hash := range removed {
|
||||
pool.all.Remove(hash)
|
||||
}
|
||||
pool.priced.Removed(len(removed))
|
||||
|
||||
// Sort all accounts with queued transactions by heartbeat
|
||||
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
|
||||
}
|
||||
sort.Sort(sort.Reverse(addresses))
|
||||
|
||||
// Drop transactions until the total is below the limit
|
||||
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
|
||||
addr := addresses[len(addresses)-1]
|
||||
list := pool.queue[addr.address]
|
||||
|
||||
addresses = addresses[:len(addresses)-1]
|
||||
|
||||
// Drop all transactions if they are less than the overflow
|
||||
if size := uint64(list.Len()); size <= drop {
|
||||
for _, tx := range list.Flatten() {
|
||||
pool.removeTx(tx.Hash(), true, true)
|
||||
}
|
||||
drop -= size
|
||||
queuedRateLimitMeter.Mark(int64(size))
|
||||
continue
|
||||
}
|
||||
// Otherwise drop only last few transactions
|
||||
txs := list.Flatten()
|
||||
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
|
||||
pool.removeTx(txs[i].Hash(), true, true)
|
||||
drop--
|
||||
queuedRateLimitMeter.Mark(1)
|
||||
for _, addr := range removedAddresses {
|
||||
_, hasPending := pool.pending[addr]
|
||||
if !hasPending {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1672,25 +1575,13 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
|||
// Delete the entire pending entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(pool.pending, addr)
|
||||
if _, ok := pool.queue[addr]; !ok {
|
||||
if _, ok := pool.queue.get(addr); !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addressByHeartbeat is an account address tagged with its last activity timestamp.
|
||||
type addressByHeartbeat struct {
|
||||
address common.Address
|
||||
heartbeat time.Time
|
||||
}
|
||||
|
||||
type addressesByHeartbeat []addressByHeartbeat
|
||||
|
||||
func (a addressesByHeartbeat) Len() int { return len(a) }
|
||||
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
||||
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// accountSet is simply a set of addresses to check for existence, and a signer
|
||||
// capable of deriving addresses from transactions.
|
||||
type accountSet struct {
|
||||
|
|
@ -1931,17 +1822,17 @@ func (pool *LegacyPool) Clear() {
|
|||
// acquire the subpool lock until the transaction addition is completed.
|
||||
|
||||
for addr := range pool.pending {
|
||||
if _, ok := pool.queue[addr]; !ok {
|
||||
if _, ok := pool.queue.get(addr); !ok {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
}
|
||||
for addr := range pool.queue {
|
||||
for _, addr := range pool.queue.addresses() {
|
||||
pool.reserver.Release(addr)
|
||||
}
|
||||
pool.all.Clear()
|
||||
pool.priced.Reheap()
|
||||
pool.pending = make(map[common.Address]*list)
|
||||
pool.queue = make(map[common.Address]*list)
|
||||
pool.queue = newQueue(pool.config, pool.signer)
|
||||
pool.pendingNonces = newNoncer(pool.currentState)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -466,8 +466,8 @@ func TestQueue(t *testing.T) {
|
|||
if _, ok := pool.pending[from].txs.items[tx.Nonce()]; ok {
|
||||
t.Error("expected transaction to be in tx pool")
|
||||
}
|
||||
if len(pool.queue) > 0 {
|
||||
t.Error("expected transaction queue to be empty. is", len(pool.queue))
|
||||
if len(pool.queue.queued) > 0 {
|
||||
t.Error("expected transaction queue to be empty. is", len(pool.queue.queued))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -492,8 +492,8 @@ func TestQueue2(t *testing.T) {
|
|||
if len(pool.pending) != 1 {
|
||||
t.Error("expected pending length to be 1, got", len(pool.pending))
|
||||
}
|
||||
if pool.queue[from].Len() != 2 {
|
||||
t.Error("expected len(queue) == 2, got", pool.queue[from].Len())
|
||||
if list, _ := pool.queue.get(from); list.Len() != 2 {
|
||||
t.Error("expected len(queue) == 2, got", list.Len())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -639,8 +639,8 @@ func TestMissingNonce(t *testing.T) {
|
|||
if len(pool.pending) != 0 {
|
||||
t.Error("expected 0 pending transactions, got", len(pool.pending))
|
||||
}
|
||||
if pool.queue[addr].Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
|
||||
if list, _ := pool.queue.get(addr); list.Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", list.Len())
|
||||
}
|
||||
if pool.all.Count() != 1 {
|
||||
t.Error("expected 1 total transactions, got", pool.all.Count())
|
||||
|
|
@ -712,8 +712,8 @@ func TestDropping(t *testing.T) {
|
|||
if pool.pending[account].Len() != 3 {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||
}
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||
}
|
||||
if pool.all.Count() != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
|
|
@ -722,8 +722,8 @@ func TestDropping(t *testing.T) {
|
|||
if pool.pending[account].Len() != 3 {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), 3)
|
||||
}
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
if list, _ := pool.queue.get(account); list.Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", list.Len(), 3)
|
||||
}
|
||||
if pool.all.Count() != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
|
|
@ -741,13 +741,14 @@ func TestDropping(t *testing.T) {
|
|||
if _, ok := pool.pending[account].txs.items[tx2.Nonce()]; ok {
|
||||
t.Errorf("out-of-fund pending transaction present: %v", tx1)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
||||
list, _ := pool.queue.get(account)
|
||||
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; !ok {
|
||||
if _, ok := list.txs.items[tx11.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx12.Nonce()]; ok {
|
||||
t.Errorf("out-of-fund queued transaction present: %v", tx11)
|
||||
}
|
||||
if pool.all.Count() != 4 {
|
||||
|
|
@ -763,10 +764,11 @@ func TestDropping(t *testing.T) {
|
|||
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
|
||||
t.Errorf("over-gased pending transaction present: %v", tx1)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
||||
list, _ = pool.queue.get(account)
|
||||
if _, ok := list.txs.items[tx10.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx11.Nonce()]; ok {
|
||||
t.Errorf("over-gased queued transaction present: %v", tx11)
|
||||
}
|
||||
if pool.all.Count() != 2 {
|
||||
|
|
@ -820,8 +822,8 @@ func TestPostponing(t *testing.T) {
|
|||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||
}
|
||||
if pool.all.Count() != len(txs) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||
|
|
@ -830,8 +832,8 @@ func TestPostponing(t *testing.T) {
|
|||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue.addresses()), 0)
|
||||
}
|
||||
if pool.all.Count() != len(txs) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||
|
|
@ -847,7 +849,8 @@ func TestPostponing(t *testing.T) {
|
|||
if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0])
|
||||
}
|
||||
if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok {
|
||||
list, _ := pool.queue.get(accs[0])
|
||||
if _, ok := list.txs.items[txs[0].Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0])
|
||||
}
|
||||
for i, tx := range txs[1:100] {
|
||||
|
|
@ -855,14 +858,14 @@ func TestPostponing(t *testing.T) {
|
|||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx)
|
||||
}
|
||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx)
|
||||
}
|
||||
} else {
|
||||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx)
|
||||
}
|
||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx)
|
||||
}
|
||||
}
|
||||
|
|
@ -872,13 +875,14 @@ func TestPostponing(t *testing.T) {
|
|||
if pool.pending[accs[1]] != nil {
|
||||
t.Errorf("invalidated account still has pending transactions")
|
||||
}
|
||||
list, _ = pool.queue.get(accs[1])
|
||||
for i, tx := range txs[100:] {
|
||||
if i%2 == 1 {
|
||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx)
|
||||
}
|
||||
} else {
|
||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := list.txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx)
|
||||
}
|
||||
}
|
||||
|
|
@ -963,13 +967,14 @@ func TestQueueAccountLimiting(t *testing.T) {
|
|||
if len(pool.pending) != 0 {
|
||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, len(pool.pending), 0)
|
||||
}
|
||||
list, _ := pool.queue.get(account)
|
||||
if i <= testTxPoolConfig.AccountQueue {
|
||||
if pool.queue[account].Len() != int(i) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), i)
|
||||
if list.Len() != int(i) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, list.Len(), i)
|
||||
}
|
||||
} else {
|
||||
if pool.queue[account].Len() != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, pool.queue[account].Len(), testTxPoolConfig.AccountQueue)
|
||||
if list.Len() != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("tx %d: queue limit mismatch: have %d, want %d", i, list.Len(), testTxPoolConfig.AccountQueue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1020,7 +1025,7 @@ func TestQueueGlobalLimiting(t *testing.T) {
|
|||
pool.addRemotesSync(txs)
|
||||
|
||||
queued := 0
|
||||
for addr, list := range pool.queue {
|
||||
for addr, list := range pool.queue.queued {
|
||||
if list.Len() > int(config.AccountQueue) {
|
||||
t.Errorf("addr %x: queued accounts overflown allowance: %d > %d", addr, list.Len(), config.AccountQueue)
|
||||
}
|
||||
|
|
@ -1179,8 +1184,8 @@ func TestPendingLimiting(t *testing.T) {
|
|||
if pool.pending[account].Len() != int(i)+1 {
|
||||
t.Errorf("tx %d: pending pool size mismatch: have %d, want %d", i, pool.pending[account].Len(), i+1)
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
|
||||
if len(pool.queue.addresses()) != 0 {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, len(pool.queue.addresses()), 0)
|
||||
}
|
||||
}
|
||||
if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
|
||||
|
|
|
|||
275
core/txpool/legacypool/queue.go
Normal file
275
core/txpool/legacypool/queue.go
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
// 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 legacypool
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// queue manages nonce-gapped transactions that have been validated but are
|
||||
// not yet processable.
|
||||
type queue struct {
|
||||
config Config
|
||||
signer types.Signer
|
||||
queued map[common.Address]*list // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
}
|
||||
|
||||
func newQueue(config Config, signer types.Signer) *queue {
|
||||
return &queue{
|
||||
signer: signer,
|
||||
config: config,
|
||||
queued: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// evictList returns the hashes of transactions that are old enough to be evicted.
|
||||
func (q *queue) evictList() []common.Hash {
|
||||
var removed []common.Hash
|
||||
for addr, list := range q.queued {
|
||||
if time.Since(q.beats[addr]) > q.config.Lifetime {
|
||||
for _, tx := range list.Flatten() {
|
||||
removed = append(removed, tx.Hash())
|
||||
}
|
||||
}
|
||||
}
|
||||
queuedEvictionMeter.Mark(int64(len(removed)))
|
||||
return removed
|
||||
}
|
||||
|
||||
func (q *queue) stats() int {
|
||||
queued := 0
|
||||
for _, list := range q.queued {
|
||||
queued += list.Len()
|
||||
}
|
||||
return queued
|
||||
}
|
||||
|
||||
func (q *queue) content() map[common.Address][]*types.Transaction {
|
||||
queued := make(map[common.Address][]*types.Transaction, len(q.queued))
|
||||
for addr, list := range q.queued {
|
||||
queued[addr] = list.Flatten()
|
||||
}
|
||||
return queued
|
||||
}
|
||||
|
||||
func (q *queue) contentFrom(addr common.Address) []*types.Transaction {
|
||||
var queued []*types.Transaction
|
||||
if list, ok := q.get(addr); ok {
|
||||
queued = list.Flatten()
|
||||
}
|
||||
return queued
|
||||
}
|
||||
|
||||
func (q *queue) get(addr common.Address) (*list, bool) {
|
||||
l, ok := q.queued[addr]
|
||||
return l, ok
|
||||
}
|
||||
|
||||
func (q *queue) bump(addr common.Address) {
|
||||
q.beats[addr] = time.Now()
|
||||
}
|
||||
|
||||
func (q *queue) addresses() []common.Address {
|
||||
addrs := make([]common.Address, 0, len(q.queued))
|
||||
for addr := range q.queued {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (q *queue) remove(addr common.Address, tx *types.Transaction) {
|
||||
if future := q.queued[addr]; future != nil {
|
||||
if txOld := future.txs.Get(tx.Nonce()); txOld != nil && txOld.Hash() != tx.Hash() {
|
||||
// Edge case, a different transaction
|
||||
// with the same nonce is in the queued, just ignore
|
||||
return
|
||||
}
|
||||
if removed, _ := future.Remove(tx); removed {
|
||||
// Reduce the queued counter
|
||||
queuedGauge.Dec(1)
|
||||
}
|
||||
if future.Empty() {
|
||||
delete(q.queued, addr)
|
||||
delete(q.beats, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *queue) add(tx *types.Transaction) (*common.Hash, error) {
|
||||
// Try to insert the transaction into the future queue
|
||||
from, _ := types.Sender(q.signer, tx) // already validated
|
||||
if q.queued[from] == nil {
|
||||
q.queued[from] = newList(false)
|
||||
}
|
||||
inserted, old := q.queued[from].Add(tx, q.config.PriceBump)
|
||||
if !inserted {
|
||||
// An older transaction was better, discard this
|
||||
queuedDiscardMeter.Mark(1)
|
||||
return nil, txpool.ErrReplaceUnderpriced
|
||||
}
|
||||
// If we never record the heartbeat, do it right now.
|
||||
if _, exist := q.beats[from]; !exist {
|
||||
q.beats[from] = time.Now()
|
||||
}
|
||||
if old == nil {
|
||||
// Nothing was replaced, bump the queued counter
|
||||
queuedGauge.Inc(1)
|
||||
return nil, nil
|
||||
}
|
||||
h := old.Hash()
|
||||
// Transaction was replaced, bump the replacement counter
|
||||
queuedReplaceMeter.Mark(1)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// promoteExecutables iterates over all accounts with queued transactions, selecting
|
||||
// for promotion any that are now executable. It also drops any transactions that are
|
||||
// deemed too old (nonce too low) or too costly (insufficient funds or over gas limit).
|
||||
//
|
||||
// Returns three lists:
|
||||
// - all transactions that were removed from the queue and selected for promotion;
|
||||
// - all other transactions that were removed from the queue and dropped;
|
||||
// - the list of addresses removed.
|
||||
func (q *queue) promoteExecutables(accounts []common.Address, gasLimit uint64, currentState *state.StateDB, nonces *noncer) ([]*types.Transaction, []common.Hash, []common.Address) {
|
||||
// Track the promotable transactions to broadcast them at once
|
||||
var (
|
||||
promotable []*types.Transaction
|
||||
dropped []common.Hash
|
||||
removedAddresses []common.Address
|
||||
)
|
||||
// Iterate over all accounts and promote any executable transactions
|
||||
for _, addr := range accounts {
|
||||
list := q.queued[addr]
|
||||
if list == nil {
|
||||
continue // Just in case someone calls with a non existing account
|
||||
}
|
||||
// Drop all transactions that are deemed too old (low nonce)
|
||||
forwards := list.Forward(currentState.GetNonce(addr))
|
||||
for _, tx := range forwards {
|
||||
dropped = append(dropped, tx.Hash())
|
||||
}
|
||||
log.Trace("Removing old queued transactions", "count", len(forwards))
|
||||
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
drops, _ := list.Filter(currentState.GetBalance(addr), gasLimit)
|
||||
for _, tx := range drops {
|
||||
dropped = append(dropped, tx.Hash())
|
||||
}
|
||||
log.Trace("Removing unpayable queued transactions", "count", len(drops))
|
||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
||||
|
||||
// Gather all executable transactions and promote them
|
||||
readies := list.Ready(nonces.get(addr))
|
||||
promotable = append(promotable, readies...)
|
||||
log.Trace("Promoting queued transactions", "count", len(promotable))
|
||||
queuedGauge.Dec(int64(len(readies)))
|
||||
|
||||
// Drop all transactions over the allowed limit
|
||||
var caps = list.Cap(int(q.config.AccountQueue))
|
||||
for _, tx := range caps {
|
||||
hash := tx.Hash()
|
||||
dropped = append(dropped, hash)
|
||||
log.Trace("Removing cap-exceeding queued transaction", "hash", hash)
|
||||
}
|
||||
queuedRateLimitMeter.Mark(int64(len(caps)))
|
||||
|
||||
// Delete the entire queue entry if it became empty.
|
||||
if list.Empty() {
|
||||
delete(q.queued, addr)
|
||||
delete(q.beats, addr)
|
||||
removedAddresses = append(removedAddresses, addr)
|
||||
}
|
||||
}
|
||||
queuedGauge.Dec(int64(len(dropped)))
|
||||
return promotable, dropped, removedAddresses
|
||||
}
|
||||
|
||||
// truncate drops the oldest transactions from the queue until the total
|
||||
// number is below the configured limit. Returns the hashes of all dropped
|
||||
// transactions and the addresses of accounts that became empty due to
|
||||
// the truncation.
|
||||
func (q *queue) truncate() ([]common.Hash, []common.Address) {
|
||||
queued := uint64(0)
|
||||
for _, list := range q.queued {
|
||||
queued += uint64(list.Len())
|
||||
}
|
||||
if queued <= q.config.GlobalQueue {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Sort all accounts with queued transactions by heartbeat
|
||||
addresses := make(addressesByHeartbeat, 0, len(q.queued))
|
||||
for addr := range q.queued {
|
||||
addresses = append(addresses, addressByHeartbeat{addr, q.beats[addr]})
|
||||
}
|
||||
sort.Sort(sort.Reverse(addresses))
|
||||
|
||||
// Drop transactions until the total is below the limit
|
||||
var (
|
||||
removed = make([]common.Hash, 0)
|
||||
removedAddresses = make([]common.Address, 0)
|
||||
)
|
||||
for drop := queued - q.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
|
||||
addr := addresses[len(addresses)-1]
|
||||
list := q.queued[addr.address]
|
||||
|
||||
addresses = addresses[:len(addresses)-1]
|
||||
|
||||
// Drop all transactions if they are less than the overflow
|
||||
if size := uint64(list.Len()); size <= drop {
|
||||
for _, tx := range list.Flatten() {
|
||||
q.remove(addr.address, tx)
|
||||
removed = append(removed, tx.Hash())
|
||||
}
|
||||
drop -= size
|
||||
queuedRateLimitMeter.Mark(int64(size))
|
||||
removedAddresses = append(removedAddresses, addr.address)
|
||||
continue
|
||||
}
|
||||
// Otherwise drop only last few transactions
|
||||
txs := list.Flatten()
|
||||
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
|
||||
q.remove(addr.address, txs[i])
|
||||
removed = append(removed, txs[i].Hash())
|
||||
drop--
|
||||
queuedRateLimitMeter.Mark(1)
|
||||
}
|
||||
}
|
||||
// No need to clear empty accounts, remove already does that
|
||||
return removed, removedAddresses
|
||||
}
|
||||
|
||||
// addressByHeartbeat is an account address tagged with its last activity timestamp.
|
||||
type addressByHeartbeat struct {
|
||||
address common.Address
|
||||
heartbeat time.Time
|
||||
}
|
||||
|
||||
type addressesByHeartbeat []addressByHeartbeat
|
||||
|
||||
func (a addressesByHeartbeat) Len() int { return len(a) }
|
||||
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
||||
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
|
@ -117,6 +117,25 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error {
|
|||
return failure
|
||||
}
|
||||
|
||||
func (journal *journal) setupWriter() error {
|
||||
if journal.writer != nil {
|
||||
if err := journal.writer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
journal.writer = nil
|
||||
}
|
||||
|
||||
// Re-open the journal file for appending
|
||||
// Use O_APPEND to ensure we always write to the end of the file
|
||||
sink, err := os.OpenFile(journal.path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
journal.writer = sink
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// insert adds the specified transaction to the local disk journal.
|
||||
func (journal *journal) insert(tx *types.Transaction) error {
|
||||
if journal.writer == nil {
|
||||
|
|
@ -177,7 +196,6 @@ func (journal *journal) rotate(all map[common.Address]types.Transactions) error
|
|||
// close flushes the transaction journal contents to disk and closes the file.
|
||||
func (journal *journal) close() error {
|
||||
var err error
|
||||
|
||||
if journal.writer != nil {
|
||||
err = journal.writer.Close()
|
||||
journal.writer = nil
|
||||
|
|
|
|||
|
|
@ -114,13 +114,14 @@ func (tracker *TxTracker) TrackAll(txs []*types.Transaction) {
|
|||
}
|
||||
|
||||
// recheck checks and returns any transactions that needs to be resubmitted.
|
||||
func (tracker *TxTracker) recheck(journalCheck bool) (resubmits []*types.Transaction, rejournal map[common.Address]types.Transactions) {
|
||||
func (tracker *TxTracker) recheck(journalCheck bool) []*types.Transaction {
|
||||
tracker.mu.Lock()
|
||||
defer tracker.mu.Unlock()
|
||||
|
||||
var (
|
||||
numStales = 0
|
||||
numOk = 0
|
||||
resubmits []*types.Transaction
|
||||
)
|
||||
for sender, txs := range tracker.byAddr {
|
||||
// Wipe the stales
|
||||
|
|
@ -141,7 +142,7 @@ func (tracker *TxTracker) recheck(journalCheck bool) (resubmits []*types.Transac
|
|||
}
|
||||
|
||||
if journalCheck { // rejournal
|
||||
rejournal = make(map[common.Address]types.Transactions)
|
||||
rejournal := make(map[common.Address]types.Transactions)
|
||||
for _, tx := range tracker.all {
|
||||
addr, _ := types.Sender(tracker.signer, tx)
|
||||
rejournal[addr] = append(rejournal[addr], tx)
|
||||
|
|
@ -153,10 +154,18 @@ func (tracker *TxTracker) recheck(journalCheck bool) (resubmits []*types.Transac
|
|||
return int(a.Nonce() - b.Nonce())
|
||||
})
|
||||
}
|
||||
// Rejournal the tracker while holding the lock. No new transactions will
|
||||
// be added to the old journal during this period, preventing any potential
|
||||
// transaction loss.
|
||||
if tracker.journal != nil {
|
||||
if err := tracker.journal.rotate(rejournal); err != nil {
|
||||
log.Warn("Transaction journal rotation failed", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
localGauge.Update(int64(len(tracker.all)))
|
||||
log.Debug("Tx tracker status", "need-resubmit", len(resubmits), "stale", numStales, "ok", numOk)
|
||||
return resubmits, rejournal
|
||||
return resubmits
|
||||
}
|
||||
|
||||
// Start implements node.Lifecycle interface
|
||||
|
|
@ -185,6 +194,12 @@ func (tracker *TxTracker) loop() {
|
|||
tracker.TrackAll(transactions)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Setup the writer for the upcoming transactions
|
||||
if err := tracker.journal.setupWriter(); err != nil {
|
||||
log.Error("Failed to setup the journal writer", "err", err)
|
||||
return
|
||||
}
|
||||
defer tracker.journal.close()
|
||||
}
|
||||
var (
|
||||
|
|
@ -196,20 +211,15 @@ func (tracker *TxTracker) loop() {
|
|||
case <-tracker.shutdownCh:
|
||||
return
|
||||
case <-timer.C:
|
||||
checkJournal := tracker.journal != nil && time.Since(lastJournal) > tracker.rejournal
|
||||
resubmits, rejournal := tracker.recheck(checkJournal)
|
||||
var rejournal bool
|
||||
if tracker.journal != nil && time.Since(lastJournal) > tracker.rejournal {
|
||||
rejournal, lastJournal = true, time.Now()
|
||||
log.Debug("Rejournal the transaction tracker")
|
||||
}
|
||||
resubmits := tracker.recheck(rejournal)
|
||||
if len(resubmits) > 0 {
|
||||
tracker.pool.Add(resubmits, false)
|
||||
}
|
||||
if checkJournal {
|
||||
// Lock to prevent journal.rotate <-> journal.insert (via TrackAll) conflicts
|
||||
tracker.mu.Lock()
|
||||
lastJournal = time.Now()
|
||||
if err := tracker.journal.rotate(rejournal); err != nil {
|
||||
log.Warn("Transaction journal rotation failed", "err", err)
|
||||
}
|
||||
tracker.mu.Unlock()
|
||||
}
|
||||
timer.Reset(recheckInterval)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,11 @@
|
|||
package locals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -146,20 +150,59 @@ func TestResubmit(t *testing.T) {
|
|||
txsA := txs[:len(txs)/2]
|
||||
txsB := txs[len(txs)/2:]
|
||||
env.pool.Add(txsA, true)
|
||||
|
||||
pending, queued := env.pool.ContentFrom(address)
|
||||
if len(pending) != len(txsA) || len(queued) != 0 {
|
||||
t.Fatalf("Unexpected txpool content: %d, %d", len(pending), len(queued))
|
||||
}
|
||||
env.tracker.TrackAll(txs)
|
||||
|
||||
resubmit, all := env.tracker.recheck(true)
|
||||
resubmit := env.tracker.recheck(true)
|
||||
if len(resubmit) != len(txsB) {
|
||||
t.Fatalf("Unexpected transactions to resubmit, got: %d, want: %d", len(resubmit), len(txsB))
|
||||
}
|
||||
if len(all) == 0 || len(all[address]) == 0 {
|
||||
t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", 0, len(txs))
|
||||
}
|
||||
if len(all[address]) != len(txs) {
|
||||
t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(all[address]), len(txs))
|
||||
env.tracker.mu.Lock()
|
||||
allCopy := maps.Clone(env.tracker.all)
|
||||
env.tracker.mu.Unlock()
|
||||
|
||||
if len(allCopy) != len(txs) {
|
||||
t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(allCopy), len(txs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJournal(t *testing.T) {
|
||||
journalPath := filepath.Join(t.TempDir(), fmt.Sprintf("%d", rand.Int63()))
|
||||
env := newTestEnv(t, 10, 0, journalPath)
|
||||
defer env.close()
|
||||
|
||||
env.tracker.Start()
|
||||
defer env.tracker.Stop()
|
||||
|
||||
txs := env.makeTxs(10)
|
||||
txsA := txs[:len(txs)/2]
|
||||
txsB := txs[len(txs)/2:]
|
||||
env.pool.Add(txsA, true)
|
||||
|
||||
pending, queued := env.pool.ContentFrom(address)
|
||||
if len(pending) != len(txsA) || len(queued) != 0 {
|
||||
t.Fatalf("Unexpected txpool content: %d, %d", len(pending), len(queued))
|
||||
}
|
||||
env.tracker.TrackAll(txsA)
|
||||
env.tracker.TrackAll(txsB)
|
||||
env.tracker.recheck(true) // manually rejournal the tracker
|
||||
|
||||
// Make sure all the transactions are properly journalled
|
||||
trackerB := New(journalPath, time.Minute, gspec.Config, env.pool)
|
||||
trackerB.journal.load(func(transactions []*types.Transaction) []error {
|
||||
trackerB.TrackAll(transactions)
|
||||
return nil
|
||||
})
|
||||
|
||||
trackerB.mu.Lock()
|
||||
allCopy := maps.Clone(trackerB.all)
|
||||
trackerB.mu.Unlock()
|
||||
|
||||
if len(allCopy) != len(txs) {
|
||||
t.Fatalf("Unexpected transactions being tracked, got: %d, want: %d", len(allCopy), len(txs))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ type BlockChain interface {
|
|||
type TxPool struct {
|
||||
subpools []SubPool // List of subpools for specialized transaction handling
|
||||
chain BlockChain
|
||||
signer types.Signer
|
||||
|
||||
stateLock sync.RWMutex // The lock for protecting state instance
|
||||
state *state.StateDB // Current state at the blockchain head
|
||||
|
|
@ -98,7 +97,6 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
|
|||
pool := &TxPool{
|
||||
subpools: subpools,
|
||||
chain: chain,
|
||||
signer: types.LatestSigner(chain.Config()),
|
||||
state: statedb,
|
||||
quit: make(chan chan error),
|
||||
term: make(chan struct{}),
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if _, err := types.Sender(signer, tx); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidSender, err)
|
||||
}
|
||||
// Limit nonce to 2^64-1 per EIP-2681
|
||||
if tx.Nonce()+1 < tx.Nonce() {
|
||||
return core.ErrNonceMax
|
||||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)
|
||||
|
|
@ -144,7 +148,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
return nil
|
||||
}
|
||||
|
||||
func ValidateBlobSidecar(tx *types.Transaction, sidecar *types.BlobTxCellSidecar, head *types.Header, opts *ValidationOptions) error {
|
||||
func ValidateBlobSidecar(tx *types.Transaction, sidecar *types.BlobTxCellSidecar, opts *ValidationOptions) error {
|
||||
if sidecar.Custody.OneCount() == 0 {
|
||||
return errors.New("blobless blob transaction")
|
||||
}
|
||||
|
|
@ -171,16 +175,14 @@ func ValidateBlobSidecar(tx *types.Transaction, sidecar *types.BlobTxCellSidecar
|
|||
return err
|
||||
}
|
||||
// Fork-specific sidecar checks, including proof verification.
|
||||
if opts.Config.IsOsaka(head.Number, head.Time) {
|
||||
if sidecar.Version == types.BlobSidecarVersion1 {
|
||||
return validateBlobSidecarOsaka(sidecar, hashes)
|
||||
} else {
|
||||
return validateBlobSidecarLegacy(sidecar, hashes)
|
||||
}
|
||||
return validateBlobSidecarLegacy(sidecar, hashes)
|
||||
}
|
||||
|
||||
func validateBlobSidecarLegacy(sidecar *types.BlobTxCellSidecar, hashes []common.Hash) error {
|
||||
if sidecar.Version != types.BlobSidecarVersion0 {
|
||||
return fmt.Errorf("invalid sidecar version pre-osaka: %v", sidecar.Version)
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes) {
|
||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes))
|
||||
}
|
||||
|
|
@ -197,9 +199,6 @@ func validateBlobSidecarLegacy(sidecar *types.BlobTxCellSidecar, hashes []common
|
|||
}
|
||||
|
||||
func validateBlobSidecarOsaka(sidecar *types.BlobTxCellSidecar, hashes []common.Hash) error {
|
||||
if sidecar.Version != types.BlobSidecarVersion1 {
|
||||
return fmt.Errorf("invalid sidecar version post-osaka: %v", sidecar.Version)
|
||||
}
|
||||
if len(sidecar.Proofs) != len(hashes)*kzg4844.CellProofsPerBlob {
|
||||
return fmt.Errorf("invalid number of %d blob proofs expected %d", len(sidecar.Proofs), len(hashes)*kzg4844.CellProofsPerBlob)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue