diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6eae7f0f53d..6677f7b8c76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,16 +15,15 @@ on: workflow_dispatch: -concurrency: - group: ${{ github.ref }} - cancel-in-progress: ${{ !contains(fromJSON('[ - "refs/heads/release/2.60", - "refs/heads/release/2.61", - "refs/heads/main" - ]'), github.ref) }} - jobs: - tests: + linux: + concurrency: + group: ci-${{ matrix.os }}-${{ github.ref }} + cancel-in-progress: >- + ${{ + !startsWith(github.ref, 'refs/heads/release/') && + github.ref != 'refs/heads/main' + }} if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} strategy: matrix: @@ -76,7 +75,14 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} continue-on-error: true -# tests-windows: +# win: +# concurrency: +# group: ci-win-${{ matrix.os }}-${{ github.ref }} +# cancel-in-progress: >- +# ${{ +# !startsWith(github.ref, 'refs/heads/release/') && +# github.ref != 'refs/heads/main' +# }} # if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} # strategy: # matrix: diff --git a/.github/workflows/kurtosis/eip7702-test.io b/.github/workflows/kurtosis/eip7702-test.io index e0ac1dff6c8..273fdd86c4f 100644 --- a/.github/workflows/kurtosis/eip7702-test.io +++ b/.github/workflows/kurtosis/eip7702-test.io @@ -18,6 +18,7 @@ tasks: prefundMinBalance: 10000000000000000000 # ensure 10 ETH walletAddressResultVar: "eoaAddress" walletPrivateKeyResultVar: "eoaPrivateKey" + randomSeed: true configVars: privateKey: "walletPrivkey" - name: sleep @@ -60,4 +61,4 @@ tasks: configVars: privateKey: "eoaPrivateKey" targetAddress: "testContractAddr" - authorizations: "| [ { codeAddress: .delegateContractAddr, signerPrivkey: .eoaPrivateKey } ]" + authorizations: "| [ { codeAddress: .delegateContractAddr, signerPrivkey: .eoaPrivateKey, nonce: 3 } ]" diff --git a/.github/workflows/kurtosis/pectra.io b/.github/workflows/kurtosis/pectra.io index a6982fd33c4..87b5def35ad 100644 --- a/.github/workflows/kurtosis/pectra.io +++ b/.github/workflows/kurtosis/pectra.io @@ -2,6 +2,7 @@ participants_matrix: el: - el_type: erigon el_image: test/erigon:current + el_log_level: "debug" cl: - cl_type: teku cl_image: consensys/teku:develop diff --git a/.github/workflows/qa-rpc-integration-tests.yml b/.github/workflows/qa-rpc-integration-tests.yml index e06faf9caa3..949de7656f4 100644 --- a/.github/workflows/qa-rpc-integration-tests.yml +++ b/.github/workflows/qa-rpc-integration-tests.yml @@ -13,6 +13,15 @@ on: # types: # - ready_for_review +concurrency: + group: ${{ github.ref }} + cancel-in-progress: >- + ${{ + !startsWith(github.ref, 'refs/heads/release/') && + github.ref != 'refs/heads/main' + }} + + jobs: integration-test-suite: runs-on: [ self-hosted, qa, RpcSpecific ] @@ -21,6 +30,7 @@ jobs: ERIGON_TESTBED_AREA: /opt/erigon-testbed ERIGON_QA_PATH: /home/qarunner/erigon-qa RPC_PAST_TEST_DIR: /opt/rpc-past-tests + ERIGON_ASSERT: true CHAIN: mainnet steps: @@ -30,7 +40,7 @@ jobs: - name: Checkout RPC Tests Repository & Install Requirements run: | rm -rf ${{ runner.workspace }}/rpc-tests - git -c advice.detachedHead=false clone --depth 1 --branch v1.41.0 https://github.com/erigontech/rpc-tests ${{runner.workspace}}/rpc-tests + git -c advice.detachedHead=false clone --depth 1 --branch release/3.0 https://github.com/erigontech/rpc-tests ${{runner.workspace}}/rpc-tests cd ${{ runner.workspace }}/rpc-tests pip3 install -r requirements.txt diff --git a/.github/workflows/qa-sync-from-scratch-minimal-node.yml b/.github/workflows/qa-sync-from-scratch-minimal-node.yml index b366eaefd90..725eb8eceed 100644 --- a/.github/workflows/qa-sync-from-scratch-minimal-node.yml +++ b/.github/workflows/qa-sync-from-scratch-minimal-node.yml @@ -26,6 +26,7 @@ jobs: TRACKING_TIME_SECONDS: 7200 # 2 hours TOTAL_TIME_SECONDS: 64800 # 18 hours CHAIN: ${{ matrix.chain }} + ERIGON_ASSERT: true steps: - name: Check out repository diff --git a/.github/workflows/qa-sync-from-scratch.yml b/.github/workflows/qa-sync-from-scratch.yml index 8d65f238487..7ec1d25b0ce 100644 --- a/.github/workflows/qa-sync-from-scratch.yml +++ b/.github/workflows/qa-sync-from-scratch.yml @@ -23,6 +23,8 @@ jobs: TRACKING_TIME_SECONDS: 7200 # 2 hours TOTAL_TIME_SECONDS: 43200 # 12 hours CHAIN: ${{ matrix.chain }} + ERIGON_ASSERT: true + steps: - name: Check out repository diff --git a/.github/workflows/qa-sync-with-externalcl.yml b/.github/workflows/qa-sync-with-externalcl.yml index 6f88173fa59..cf02e0fb965 100644 --- a/.github/workflows/qa-sync-with-externalcl.yml +++ b/.github/workflows/qa-sync-with-externalcl.yml @@ -30,6 +30,7 @@ jobs: ERIGON_QA_PATH: /home/qarunner/erigon-qa TRACKING_TIME_SECONDS: 3600 # 1 hour TOTAL_TIME_SECONDS: 25200 # 7 hours + ERIGON_ASSERT: true steps: - name: Check out repository diff --git a/.github/workflows/test-integration-caplin.yml b/.github/workflows/test-integration-caplin.yml index baf3f2516f0..b14536f1450 100644 --- a/.github/workflows/test-integration-caplin.yml +++ b/.github/workflows/test-integration-caplin.yml @@ -13,7 +13,7 @@ on: - reopened - synchronize - ready_for_review - + jobs: tests: strategy: diff --git a/.github/workflows/test-integration-erigon.yml b/.github/workflows/test-integration-erigon.yml index 61cf33f0ee6..60f0e809913 100644 --- a/.github/workflows/test-integration-erigon.yml +++ b/.github/workflows/test-integration-erigon.yml @@ -43,6 +43,14 @@ jobs: tests-mac-linux: needs: source-of-changes + concurrency: + group: tests-mac-linux-${{ matrix.os }}-${{ github.ref }} + cancel-in-progress: >- + ${{ + needs.source-of-changes.outputs.changed_files != 'true' && + !startsWith(github.ref, 'refs/heads/release/') && + github.ref != 'refs/heads/main' + }} strategy: matrix: os: @@ -88,6 +96,14 @@ jobs: tests-windows: needs: source-of-changes + concurrency: + group: tests-windows-${{ matrix.os }}-${{ github.ref }} + cancel-in-progress: >- + ${{ + needs.source-of-changes.outputs.changed_files != 'true' && + !startsWith(github.ref, 'refs/heads/release/') && + github.ref != 'refs/heads/main' + }} strategy: matrix: os: [ windows-2025 ] diff --git a/ChangeLog.md b/ChangeLog.md index 8aff09d6ac6..ee00d368193 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,18 +1,110 @@ ChangeLog --------- -## v3.0.3 (in development) +## v3.0.5 (in development) + +### Milestone + +https://github.com/erigontech/erigon/milestone/44 + +## v3.0.4 + +### Milestone + +https://github.com/erigontech/erigon/milestone/43 + +**Improvements:** + +- disable diagnostics by default by @yperbasis in https://github.com/erigontech/erigon/pull/14953 +- less disk IO during files merge by @AskAlexSharov in https://github.com/erigontech/erigon/pull/14901 +- stage_custom_trace: to produce indices by @AskAlexSharov in https://github.com/erigontech/erigon/pull/14879 +- persist receipts - external rpcd support by @AskAlexSharov in https://github.com/erigontech/erigon/pull/15004 +- support `NO_PRUNE` env var by @AskAlexSharov in https://github.com/erigontech/erigon/pull/15131 +- cmd: Increase default `db.size.limit` by @AskAlexSharov in https://github.com/erigontech/erigon/pull/15170 +- notify rpcd when e3 files change by @sudeepdino008 in https://github.com/erigontech/erigon/pull/15044 +- mdbx v0.13.6 by @JkLondon in https://github.com/erigontech/erigon/pull/15112 + +**Bugfixes:** + +- Fix issues reported in snapshot processing to fix sync issues due to bugs in sync event and checkpoint snapshot production by @eastorski in https://github.com/erigontech/erigon/pull/14887, https://github.com/erigontech/erigon/pull/14947, https://github.com/erigontech/erigon/pull/14951 +- Update go-libutp for AUR build error by @anacrolix in https://github.com/erigontech/erigon/pull/14892 +- Caplin: fix occassional mev-boost bug by @Giulio2002 in https://github.com/erigontech/erigon/pull/14991 +- Erigon: optimistic inclusion for deep reorgs #14875 by @Giulio2002 in https://github.com/erigontech/erigon/pull/14876 +- Caplin: Fix misc issues after electra (#14910) by @domiwei in https://github.com/erigontech/erigon/pull/14935 +- fix erigon seg retire to handle incomplete merges by @sudeepdino008 in https://github.com/erigontech/erigon/pull/15003 +- fast failing if version string contains "." (v1.0- v2.23 for ex) by @JkLondon in https://github.com/erigontech/erigon/pull/15048 +- prune mode flag parsing and `String()`-ing by @awskii in https://github.com/erigontech/erigon/pull/14882 +- rpcdaemon: fix txNum at GetReceipt call in getLogsV3 by @lupin012 in https://github.com/erigontech/erigon/pull/14986 + +**Full Changelog**: https://github.com/erigontech/erigon/compare/v3.0.3...v3.0.4 + +## v3.0.3 ### Milestone https://github.com/erigontech/erigon/milestone/42 +**RPC fixes:** + +Polygon users who have previously run migration steps to fix incorrect logIndex related to state sync transactions released in 3.0.2 are advised to run the migration steps again and add the --polygon.logindex flag to their flags for a complete mitigation. + +**Improvements:** + +- consensus: Add syscall failure scenarios (#14403) by @somnathb1 in https://github.com/erigontech/erigon/pull/14818 +- consensus: validate fixed lengths in abi decoding EIP-6110 deposit requests by @somnathb1 in https://github.com/erigontech/erigon/pull/14823 +- historical receipts persistency (optional) by @AskAlexSharov in https://github.com/erigontech/erigon/pull/14781 +- reduce dependency on github; download snapshot hashes from R2 by @wmitsuda in https://github.com/erigontech/erigon/pull/14849 + +**Bugfixes:** + +- eth, execution: Use block level gasPool in serial execution (#14761) by @somnathb1 in https://github.com/erigontech/erigon/pull/14820 +- no greedy prune on chain-tip (node did fall behind periodically) by @AskAlexSharov in https://github.com/erigontech/erigon/pull/14782 +- fixed performance and ordering issues with the [RPC fix from 3.0.2](https://github.com/erigontech/erigon/releases/tag/v3.0.2) by @mh0lt and @shohamc1 in https://github.com/erigontech/erigon/pull/14842 https://github.com/erigontech/erigon/pull/14785 https://github.com/erigontech/erigon/pull/14790 + +**Full Changelog**: https://github.com/erigontech/erigon/compare/v3.0.2...v3.0.3 + ## v3.0.2 ### Milestone https://github.com/erigontech/erigon/milestone/41 +**Gnosis users: this is a required update for the upcoming Pectra hardfork scheduled for 30 April 2025** + +**RPC fixes:** + +Previous versions of Erigon 3 have two bugs regarding handling of state sync events on Polygon chains: +- Incorrect `logIndex` on all state sync transaction logs +- Missing log events when using `eth_getLogs` with filters + +A proper fix has been implemented and will be progressively rolled out in Erigon 3.1 and 3.2 (track issue [here](https://github.com/erigontech/erigon/issues/14003)). A temporary workaround has been introduced if these issues are critical for your use-case. This requires regenerating receipts for all transactions on the chain. The procedure is as follows: +1. Shutdown Erigon and all rpcdaemon processes +2. Update binaries Erigon release +3. `erigon seg rm-state-snapshots --domain=receipt --datadir ` +4. `integration stage_custom_trace --datadir --chain --bor.heimdall ` + - For machines with many cores, you can add ` --exec.workers=` to improve performance (default is `7`) +5. Once complete, `rm -rf /chaindata` +6. Start Erigon + +**Improvements:** + +- Schedule Pectra hard fork for Gnosis Mainnet (#14521) by @somnathb1 in https://github.com/erigontech/erigon/pull/14523 +- params: Use no padding in minor version (#14588) by @somnathb1 in https://github.com/erigontech/erigon/pull/14594 +- recent receipts persistence: to reduce RPC latency by @AskAlexSharov in https://github.com/erigontech/erigon/pull/14532 +- historical `eth_getProof` experimental flag by @Giulio2002 in https://github.com/erigontech/erigon/pull/14568 +- Enhance efficiency of attestation selection (#14624) by @Giulio2002 in https://github.com/erigontech/erigon/pull/14633 +- EngineAPI: recover from missing chain segments (#14579) by @Giulio2002 in https://github.com/erigontech/erigon/pull/14589 +- remove unsafe closing of subsumed files in mergeLoopStep by @sudeepdino008 in https://github.com/erigontech/erigon/pull/14557 + +**Bugfixes:** + +- execution: fix missing log notifications when flushing extending fork… by @Giulio2002 in https://github.com/erigontech/erigon/pull/14578 +- EthereumExecution: fix canonical chain routine (#14580) by @Giulio2002 in https://github.com/erigontech/erigon/pull/14592 +- caplin: fix parsing topics (#14543) by @Giulio2002 in https://github.com/erigontech/erigon/pull/14593 +- Fix `trace_transaction` for Polygon chains (#14470) by @shohamc1 in https://github.com/erigontech/erigon/pull/14530 + +**Full Changelog**: https://github.com/erigontech/erigon/compare/v3.0.1...v3.0.2 + ## v3.0.1 **Improvements:** diff --git a/cl/beacon/handler/block_production.go b/cl/beacon/handler/block_production.go index 0700595cc45..fd1c4437770 100644 --- a/cl/beacon/handler/block_production.go +++ b/cl/beacon/handler/block_production.go @@ -573,6 +573,10 @@ func (a *ApiHandler) produceBeaconBody( if finalizedHash == (libcommon.Hash{}) { finalizedHash = head // probably fuck up fcu for EL but not a big deal. } + safeHash := a.forkchoiceStore.GetEth1Hash(baseState.CurrentJustifiedCheckpoint().Root) + if safeHash == (libcommon.Hash{}) { + safeHash = head + } proposerIndex, err := baseState.GetBeaconProposerIndexForSlot(targetSlot) if err != nil { return nil, 0, err @@ -616,6 +620,7 @@ func (a *ApiHandler) produceBeaconBody( idBytes, err := a.engine.ForkChoiceUpdate( ctx, finalizedHash, + safeHash, head, &engine_types.PayloadAttributes{ Timestamp: hexutil.Uint64(latestExecutionPayload.Time + secsDiff), @@ -1207,8 +1212,9 @@ func (a *ApiHandler) storeBlockAndBlobs( if err := a.forkchoiceStore.OnBlock(ctx, block, true, true, false); err != nil { return err } - finalizedBlockRoot := a.forkchoiceStore.FinalizedCheckpoint().Root - if _, err := a.engine.ForkChoiceUpdate(ctx, a.forkchoiceStore.GetEth1Hash(finalizedBlockRoot), a.forkchoiceStore.GetEth1Hash(blockRoot), nil); err != nil { + finalizedHash := a.forkchoiceStore.GetEth1Hash(a.forkchoiceStore.FinalizedCheckpoint().Root) + safeHash := a.forkchoiceStore.GetEth1Hash(a.forkchoiceStore.JustifiedCheckpoint().Root) + if _, err := a.engine.ForkChoiceUpdate(ctx, finalizedHash, safeHash, a.forkchoiceStore.GetEth1Hash(blockRoot), nil); err != nil { return err } headState, err := a.forkchoiceStore.GetStateAtBlockRoot(blockRoot, false) diff --git a/cl/phase1/execution_client/block_collector/block_collector.go b/cl/phase1/execution_client/block_collector/block_collector.go index 536306fe02b..893662ab0c3 100644 --- a/cl/phase1/execution_client/block_collector/block_collector.go +++ b/cl/phase1/execution_client/block_collector/block_collector.go @@ -148,7 +148,7 @@ func (b *blockCollector) Flush(ctx context.Context) error { isForkchoiceNeeded := currentHeader == nil || blocksBatch[len(blocksBatch)-1].NumberU64() > currentHeader.Number.Uint64() if inserted >= b.syncBackLoop { if isForkchoiceNeeded { - if _, err := b.engine.ForkChoiceUpdate(ctx, lastBlockHash, lastBlockHash, nil); err != nil { + if _, err := b.engine.ForkChoiceUpdate(ctx, lastBlockHash, lastBlockHash, lastBlockHash, nil); err != nil { b.logger.Warn("failed to update fork choice", "err", err) } } diff --git a/cl/phase1/execution_client/execution_client_direct.go b/cl/phase1/execution_client/execution_client_direct.go index 5c223ab3e06..0e7dd9a76be 100644 --- a/cl/phase1/execution_client/execution_client_direct.go +++ b/cl/phase1/execution_client/execution_client_direct.go @@ -113,8 +113,8 @@ func (cc *ExecutionClientDirect) NewPayload( return PayloadStatusNone, errors.New("unexpected status") } -func (cc *ExecutionClientDirect) ForkChoiceUpdate(ctx context.Context, finalized libcommon.Hash, head libcommon.Hash, attr *engine_types.PayloadAttributes) ([]byte, error) { - status, _, _, err := cc.chainRW.UpdateForkChoice(ctx, head, head, finalized) +func (cc *ExecutionClientDirect) ForkChoiceUpdate(ctx context.Context, finalized, safe, head libcommon.Hash, attr *engine_types.PayloadAttributes) ([]byte, error) { + status, _, _, err := cc.chainRW.UpdateForkChoice(ctx, head, safe, finalized) if err != nil { return nil, fmt.Errorf("execution Client RPC failed to retrieve ForkChoiceUpdate response, err: %w", err) } diff --git a/cl/phase1/execution_client/execution_client_rpc.go b/cl/phase1/execution_client/execution_client_rpc.go index 223705a58e8..a5f649a9182 100644 --- a/cl/phase1/execution_client/execution_client_rpc.go +++ b/cl/phase1/execution_client/execution_client_rpc.go @@ -154,10 +154,10 @@ func (cc *ExecutionClientRpc) NewPayload( return newPayloadStatusByEngineStatus(payloadStatus.Status), checkPayloadStatus(payloadStatus) } -func (cc *ExecutionClientRpc) ForkChoiceUpdate(ctx context.Context, finalized libcommon.Hash, head libcommon.Hash, attributes *engine_types.PayloadAttributes) ([]byte, error) { +func (cc *ExecutionClientRpc) ForkChoiceUpdate(ctx context.Context, finalized, safe, head libcommon.Hash, attributes *engine_types.PayloadAttributes) ([]byte, error) { forkChoiceRequest := engine_types.ForkChoiceState{ HeadHash: head, - SafeBlockHash: head, + SafeBlockHash: safe, FinalizedBlockHash: finalized, } forkChoiceResp := &engine_types.ForkChoiceUpdatedResponse{} diff --git a/cl/phase1/execution_client/execution_engine_mock.go b/cl/phase1/execution_client/execution_engine_mock.go index abd9c37154d..b5282972561 100644 --- a/cl/phase1/execution_client/execution_engine_mock.go +++ b/cl/phase1/execution_client/execution_engine_mock.go @@ -87,18 +87,18 @@ func (c *MockExecutionEngineCurrentHeaderCall) DoAndReturn(f func(context.Contex } // ForkChoiceUpdate mocks base method. -func (m *MockExecutionEngine) ForkChoiceUpdate(ctx context.Context, finalized, head common.Hash, attributes *engine_types.PayloadAttributes) ([]byte, error) { +func (m *MockExecutionEngine) ForkChoiceUpdate(ctx context.Context, finalized, safe, head common.Hash, attributes *engine_types.PayloadAttributes) ([]byte, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ForkChoiceUpdate", ctx, finalized, head, attributes) + ret := m.ctrl.Call(m, "ForkChoiceUpdate", ctx, finalized, safe, head, attributes) ret0, _ := ret[0].([]byte) ret1, _ := ret[1].(error) return ret0, ret1 } // ForkChoiceUpdate indicates an expected call of ForkChoiceUpdate. -func (mr *MockExecutionEngineMockRecorder) ForkChoiceUpdate(ctx, finalized, head, attributes any) *MockExecutionEngineForkChoiceUpdateCall { +func (mr *MockExecutionEngineMockRecorder) ForkChoiceUpdate(ctx, finalized, safe, head, attributes any) *MockExecutionEngineForkChoiceUpdateCall { mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForkChoiceUpdate", reflect.TypeOf((*MockExecutionEngine)(nil).ForkChoiceUpdate), ctx, finalized, head, attributes) + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForkChoiceUpdate", reflect.TypeOf((*MockExecutionEngine)(nil).ForkChoiceUpdate), ctx, finalized, safe, head, attributes) return &MockExecutionEngineForkChoiceUpdateCall{Call: call} } @@ -114,13 +114,13 @@ func (c *MockExecutionEngineForkChoiceUpdateCall) Return(arg0 []byte, arg1 error } // Do rewrite *gomock.Call.Do -func (c *MockExecutionEngineForkChoiceUpdateCall) Do(f func(context.Context, common.Hash, common.Hash, *engine_types.PayloadAttributes) ([]byte, error)) *MockExecutionEngineForkChoiceUpdateCall { +func (c *MockExecutionEngineForkChoiceUpdateCall) Do(f func(context.Context, common.Hash, common.Hash, common.Hash, *engine_types.PayloadAttributes) ([]byte, error)) *MockExecutionEngineForkChoiceUpdateCall { c.Call = c.Call.Do(f) return c } // DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockExecutionEngineForkChoiceUpdateCall) DoAndReturn(f func(context.Context, common.Hash, common.Hash, *engine_types.PayloadAttributes) ([]byte, error)) *MockExecutionEngineForkChoiceUpdateCall { +func (c *MockExecutionEngineForkChoiceUpdateCall) DoAndReturn(f func(context.Context, common.Hash, common.Hash, common.Hash, *engine_types.PayloadAttributes) ([]byte, error)) *MockExecutionEngineForkChoiceUpdateCall { c.Call = c.Call.DoAndReturn(f) return c } diff --git a/cl/phase1/execution_client/interface.go b/cl/phase1/execution_client/interface.go index ddcbcee17e3..ba20d837ab1 100644 --- a/cl/phase1/execution_client/interface.go +++ b/cl/phase1/execution_client/interface.go @@ -37,7 +37,7 @@ var errContextExceeded = "rpc error: code = DeadlineExceeded desc = context dead //go:generate mockgen -typed=true -source=./interface.go -destination=./execution_engine_mock.go -package=execution_client . ExecutionEngine type ExecutionEngine interface { NewPayload(ctx context.Context, payload *cltypes.Eth1Block, beaconParentRoot *libcommon.Hash, versionedHashes []libcommon.Hash, executionRequestsList []hexutility.Bytes) (PayloadStatus, error) - ForkChoiceUpdate(ctx context.Context, finalized libcommon.Hash, head libcommon.Hash, attributes *engine_types.PayloadAttributes) ([]byte, error) + ForkChoiceUpdate(ctx context.Context, finalized, safe, head libcommon.Hash, attributes *engine_types.PayloadAttributes) ([]byte, error) SupportInsertion() bool InsertBlocks(ctx context.Context, blocks []*types.Block, wait bool) error InsertBlock(ctx context.Context, block *types.Block) error diff --git a/cl/phase1/stages/forkchoice.go b/cl/phase1/stages/forkchoice.go index a9a17ab6fa3..dbb958d99bc 100644 --- a/cl/phase1/stages/forkchoice.go +++ b/cl/phase1/stages/forkchoice.go @@ -61,12 +61,14 @@ func computeAndNotifyServicesOfNewForkChoice(ctx context.Context, logger log.Log // Perform fork choice update if the engine is available if cfg.forkChoice.Engine() != nil { finalizedCheckpoint := cfg.forkChoice.FinalizedCheckpoint() + justifiedCheckpoint := cfg.forkChoice.JustifiedCheckpoint() logger.Debug("Caplin is sending forkchoice") // Run fork choice update with finalized checkpoint and head if _, err = cfg.forkChoice.Engine().ForkChoiceUpdate( ctx, cfg.forkChoice.GetEth1Hash(finalizedCheckpoint.Root), + cfg.forkChoice.GetEth1Hash(justifiedCheckpoint.Root), cfg.forkChoice.GetEth1Hash(headRoot), nil, ); err != nil { err = fmt.Errorf("failed to run forkchoice: %w", err) diff --git a/cmd/devnet/main.go b/cmd/devnet/main.go index 8456fc4071f..654acbe8688 100644 --- a/cmd/devnet/main.go +++ b/cmd/devnet/main.go @@ -301,7 +301,7 @@ func allScenarios(cliCtx *cli.Context, runCtx devnet.Context) scenarios.Scenario const sendValue uint64 = 10000 return scenarios.Scenarios{ - "dynamic-tx-node-0": { + "dynamic-tx-node-0": &scenarios.Scenario{ Context: runCtx.WithCurrentNetwork(0).WithCurrentNode(0), Steps: []*scenarios.Step{ {Text: "InitSubscriptions", Args: []any{[]requests.SubMethod{requests.Methods.ETHNewHeads}}}, @@ -311,7 +311,7 @@ func allScenarios(cliCtx *cli.Context, runCtx devnet.Context) scenarios.Scenario {Text: "AwaitBlocks", Args: []any{2 * time.Second}}, }, }, - "dynamic-tx-any-node": { + "dynamic-tx-any-node": &scenarios.Scenario{ Context: runCtx.WithCurrentNetwork(0), Steps: []*scenarios.Step{ {Text: "InitSubscriptions", Args: []any{[]requests.SubMethod{requests.Methods.ETHNewHeads}}}, @@ -321,14 +321,14 @@ func allScenarios(cliCtx *cli.Context, runCtx devnet.Context) scenarios.Scenario {Text: "AwaitBlocks", Args: []any{2 * time.Second}}, }, }, - "call-contract": { + "call-contract": &scenarios.Scenario{ Context: runCtx.WithCurrentNetwork(0), Steps: []*scenarios.Step{ {Text: "InitSubscriptions", Args: []any{[]requests.SubMethod{requests.Methods.ETHNewHeads}}}, {Text: "DeployAndCallLogSubscriber", Args: []any{accounts.DevAddress}}, }, }, - "state-sync": { + "state-sync": &scenarios.Scenario{ Steps: []*scenarios.Step{ {Text: "InitSubscriptions", Args: []any{[]requests.SubMethod{requests.Methods.ETHNewHeads}}}, {Text: "CreateAccountWithFunds", Args: []any{networkname.Dev, "root-funder", 200.0}}, @@ -340,7 +340,7 @@ func allScenarios(cliCtx *cli.Context, runCtx devnet.Context) scenarios.Scenario {Text: "BatchProcessRootTransfers", Args: []any{"root-funder", 1, 10, 2, 2}}, }, }, - "child-chain-exit": { + "child-chain-exit": &scenarios.Scenario{ Steps: []*scenarios.Step{ {Text: "CreateAccountWithFunds", Args: []any{networkname.Dev, "root-funder", 200.0}}, {Text: "CreateAccountWithFunds", Args: []any{networkname.BorDevnet, "child-funder", 200.0}}, @@ -350,7 +350,7 @@ func allScenarios(cliCtx *cli.Context, runCtx devnet.Context) scenarios.Scenario //{Text: "BatchProcessTransfers", Args: []any{"child-funder", 1, 10, 2, 2}}, }, }, - "block-production": { + "block-production": &scenarios.Scenario{ Steps: []*scenarios.Step{ {Text: "SendTxLoad", Args: []any{recipientAddress, accounts.DevAddress, sendValue, cliCtx.Uint(txCountFlag.Name)}}, }, diff --git a/cmd/devnet/services/polygon/heimdall.go b/cmd/devnet/services/polygon/heimdall.go index 8eaebe924c8..9d448631fc9 100644 --- a/cmd/devnet/services/polygon/heimdall.go +++ b/cmd/devnet/services/polygon/heimdall.go @@ -221,6 +221,10 @@ func (h *Heimdall) getSpanOverrideHeight() uint64 { //MainChain: 8664000 } +func (h *Heimdall) FetchChainManagerStatus(ctx context.Context) (*heimdall.ChainManagerStatus, error) { + return nil, errors.New("TODO") +} + func (h *Heimdall) FetchStatus(ctx context.Context) (*heimdall.Status, error) { return nil, errors.New("TODO") } @@ -265,10 +269,6 @@ func (h *Heimdall) FetchStateSyncEvents(ctx context.Context, fromID uint64, to t return nil, errors.New("TODO") } -func (h *Heimdall) FetchStateSyncEvent(ctx context.Context, id uint64) (*heimdall.EventRecordWithTime, error) { - return nil, errors.New("TODO") -} - func (h *Heimdall) Close() { h.unsubscribe() } diff --git a/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go b/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go index 9c510a91938..342319723cd 100644 --- a/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go +++ b/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go @@ -80,6 +80,9 @@ func (noopBridgeStore) LastFrozenEventBlockNum() uint64 { func (noopBridgeStore) EventTxnToBlockNum(ctx context.Context, borTxHash libcommon.Hash) (uint64, bool, error) { return 0, false, errors.New("noop") } +func (noopBridgeStore) EventsByTimeframe(ctx context.Context, timeFrom, timeTo uint64) ([][]byte, error) { + return nil, errors.New("noop") +} func (noopBridgeStore) Events(ctx context.Context, start, end uint64) ([][]byte, error) { return nil, errors.New("noop") } @@ -224,8 +227,8 @@ func (h *HeimdallSimulator) FetchStateSyncEvents(_ context.Context, fromId uint6 return events, err } -func (h *HeimdallSimulator) FetchStateSyncEvent(ctx context.Context, id uint64) (*heimdall.EventRecordWithTime, error) { - return nil, errors.New("method FetchStateSyncEvent not implemented") +func (h *HeimdallSimulator) FetchChainManagerStatus(ctx context.Context) (*heimdall.ChainManagerStatus, error) { + return nil, errors.New("method FetchChainManagerStatus not implemented") } func (h *HeimdallSimulator) FetchStatus(ctx context.Context) (*heimdall.Status, error) { diff --git a/cmd/integration/commands/flags.go b/cmd/integration/commands/flags.go index 3d31d26601a..6cbf92ddfd5 100644 --- a/cmd/integration/commands/flags.go +++ b/cmd/integration/commands/flags.go @@ -65,7 +65,7 @@ func withConfig(cmd *cobra.Command) { func withMining(cmd *cobra.Command) { cmd.Flags().Bool("mine", false, "Enable mining") cmd.Flags().StringArray("miner.notify", nil, "Comma separated HTTP URL list to notify of new work packages") - cmd.Flags().Uint64("miner.gaslimit", ethconfig.Defaults.Miner.GasLimit, "Target gas limit for mined blocks") + cmd.Flags().Uint64("miner.gaslimit", ethconfig.DefaultMinerGasLimit, "Target gas limit for mined blocks") cmd.Flags().Int64("miner.gasprice", ethconfig.Defaults.Miner.GasPrice.Int64(), "Target gas price for mined blocks") cmd.Flags().String("miner.etherbase", "0", "Public address for block mining rewards (default = first account") cmd.Flags().String("miner.extradata", "", "Block extra data set by the miner (default = client version)") diff --git a/cmd/integration/commands/stages.go b/cmd/integration/commands/stages.go index 8096b96a3fb..2f8f7e33dcd 100644 --- a/cmd/integration/commands/stages.go +++ b/cmd/integration/commands/stages.go @@ -29,8 +29,6 @@ import ( "sync" "time" - "github.com/erigontech/erigon/consensus/parlia" - "github.com/c2h5oh/datasize" "github.com/erigontech/mdbx-go/mdbx" "github.com/erigontech/secp256k1" @@ -46,7 +44,6 @@ import ( "github.com/erigontech/erigon-lib/config3" "github.com/erigontech/erigon-lib/downloader" "github.com/erigontech/erigon-lib/kv" - "github.com/erigontech/erigon-lib/kv/backup" "github.com/erigontech/erigon-lib/kv/kvcfg" "github.com/erigontech/erigon-lib/kv/rawdbv3" "github.com/erigontech/erigon-lib/kv/temporal" @@ -57,6 +54,7 @@ import ( "github.com/erigontech/erigon/cl/clparams" "github.com/erigontech/erigon/cmd/hack/tool/fromdb" "github.com/erigontech/erigon/consensus" + "github.com/erigontech/erigon/consensus/parlia" "github.com/erigontech/erigon/core" "github.com/erigontech/erigon/core/rawdb" "github.com/erigontech/erigon/core/rawdb/blockio" @@ -130,27 +128,6 @@ var cmdStageHeaders = &cobra.Command{ }, } -var cmdStageBorHeimdall = &cobra.Command{ - Use: "stage_bor_heimdall", - Short: "", - Run: func(cmd *cobra.Command, args []string) { - logger := debug.SetupCobra(cmd, "integration") - db, err := openDB(dbCfg(kv.ChainDB, chaindata), true, logger) - if err != nil { - logger.Error("Opening DB", "error", err) - return - } - defer db.Close() - - if err := stageBorHeimdall(db, cmd.Context(), unwindTypes, logger); err != nil { - if !errors.Is(err, context.Canceled) { - logger.Error(err.Error()) - } - return - } - }, -} - var cmdStageBodies = &cobra.Command{ Use: "stage_bodies", Short: "", @@ -507,16 +484,6 @@ func init() { withChaosMonkey(cmdStageHeaders) rootCmd.AddCommand(cmdStageHeaders) - withConfig(cmdStageBorHeimdall) - withDataDir(cmdStageBorHeimdall) - withReset(cmdStageBorHeimdall) - withUnwind(cmdStageBorHeimdall) - withUnwindTypes(cmdStageBorHeimdall) - withChain(cmdStageBorHeimdall) - withHeimdall(cmdStageBorHeimdall) - withChaosMonkey(cmdStageBorHeimdall) - rootCmd.AddCommand(cmdStageBorHeimdall) - withConfig(cmdStageBodies) withDataDir(cmdStageBodies) withUnwind(cmdStageBodies) @@ -671,11 +638,8 @@ func stageHeaders(db kv.TemporalRwDB, ctx context.Context, logger log.Logger) er chainConfig := fromdb.ChainConfig(db) if integritySlow { - if err := db.View(ctx, func(tx kv.Tx) error { - log.Info("[integrity] no gaps in canonical headers") - integrity.NoGapsInCanonicalHeaders(tx, ctx, br) - return nil - }); err != nil { + log.Info("[integrity] no gaps in canonical headers") + if err := integrity.NoGapsInCanonicalHeaders(ctx, db, br, true); err != nil { return err } return nil @@ -691,7 +655,6 @@ func stageHeaders(db kv.TemporalRwDB, ctx context.Context, logger log.Logger) er if err := reset2.ResetBlocks(tx, br, bw, dirs, *chainConfig, engine, logger); err != nil { return fmt.Errorf("resetting blocks: %w", err) } - return nil } @@ -750,83 +713,6 @@ func stageHeaders(db kv.TemporalRwDB, ctx context.Context, logger log.Logger) er }) } -func stageBorHeimdall(db kv.TemporalRwDB, ctx context.Context, unwindTypes []string, logger log.Logger) error { - _, engine, _, sync, _, miningState := newSync(ctx, db, nil /* miningConfig */, logger) - chainConfig := fromdb.ChainConfig(db) - - heimdallClient := engine.(*bor.Bor).HeimdallClient - - if reset { - if err := reset2.ResetBorHeimdall(ctx, nil, db); err != nil { - return err - } - return nil - } - if unwind > 0 { - sn, borSn, _, _, _, bridgeStore, heimdallStore, err := allSnapshots(ctx, db, logger) - if err != nil { - return err - } - defer sn.Close() - defer borSn.Close() - - stageState := stage(sync, nil, db, stages.BorHeimdall) - - snapshotsMaxBlock := borSn.BlocksAvailable() - if unwind <= snapshotsMaxBlock { - return fmt.Errorf("cannot unwind past snapshots max block: %d", snapshotsMaxBlock) - } - - if unwind > stageState.BlockNumber { - return fmt.Errorf("cannot unwind to a point beyond stage: %d", stageState.BlockNumber) - } - - unwindState := sync.NewUnwindState(stages.BorHeimdall, stageState.BlockNumber-unwind, stageState.BlockNumber, true, false) - cfg := stagedsync.StageBorHeimdallCfg(db, nil, miningState, *chainConfig, nil, heimdallStore, bridgeStore, nil, nil, nil, nil, nil, false, unwindTypes) - if err := stagedsync.BorHeimdallUnwind(unwindState, ctx, stageState, nil, cfg); err != nil { - return err - } - - stageProgress, err := stagedsync.BorHeimdallStageProgress(nil, cfg) - if err != nil { - return fmt.Errorf("re-read bor heimdall progress: %w", err) - } - - logger.Info("progress", "bor heimdall", stageProgress) - return nil - } - - _, _, _, _, _, bridgeStore, heimdallStore, err := allSnapshots(ctx, db, logger) - if err != nil { - return err - } - blockReader, _ := blocksIO(db, logger) - var ( - snapDb kv.RwDB - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot] - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address] - ) - if bor, ok := engine.(*bor.Bor); ok { - snapDb = bor.DB - recents = bor.Recents - signatures = bor.Signatures - } - cfg := stagedsync.StageBorHeimdallCfg(db, snapDb, miningState, *chainConfig, heimdallClient, heimdallStore, bridgeStore, blockReader, nil, nil, recents, signatures, false, unwindTypes) - - stageState := stage(sync, nil, db, stages.BorHeimdall) - if err := stagedsync.BorHeimdallForward(stageState, sync, ctx, nil, cfg, logger); err != nil { - return err - } - - stageProgress, err := stagedsync.BorHeimdallStageProgress(nil, cfg) - if err != nil { - return fmt.Errorf("re-read bor heimdall progress: %w", err) - } - - logger.Info("progress", "bor heimdall", stageProgress) - return nil -} - func stageBodies(db kv.TemporalRwDB, ctx context.Context, logger log.Logger) error { chainConfig := fromdb.ChainConfig(db) _, _, _, sync, _, _ := newSync(ctx, db, nil /* miningConfig */, logger) @@ -880,7 +766,7 @@ func stagePolygonSync(db kv.TemporalRwDB, ctx context.Context, logger log.Logger stageState := stage(stageSync, tx, nil, stages.PolygonSync) cfg := stagedsync.NewPolygonSyncStageCfg(ðconfig.Defaults, logger, chainConfig, nil, heimdallClient, - heimdallStore, bridgeStore, nil, 0, nil, blockReader, nil, 0, unwindTypes, nil /* notifications */, nil) + heimdallStore, bridgeStore, nil, 0, nil, blockReader, nil, 0, unwindTypes, nil /* notifications */, nil, nil) // we only need blockReader and blockWriter (blockWriter is constructed in NewPolygonSyncStageCfg) if unwind > 0 { u := stageSync.NewUnwindState(stageState.ID, stageState.BlockNumber-unwind, stageState.BlockNumber, true, false) @@ -1019,7 +905,7 @@ func stageExec(db kv.TemporalRwDB, ctx context.Context, logger log.Logger) error s.CurrentSyncCycle.IsInitialCycle = false } - genesis := core.GenesisBlockByChainName(chain) + genesis := readGenesis(chain) br, _ := blocksIO(db, logger) notifications := shards.NewNotifications(nil) @@ -1030,7 +916,7 @@ func stageExec(db kv.TemporalRwDB, ctx context.Context, logger log.Logger) error if unwind > 0 { if err := db.View(ctx, func(tx kv.Tx) error { - minUnwindableBlockNum, _, err := tx.(libstate.HasAggTx).AggTx().(*libstate.AggregatorRoTx).CanUnwindBeforeBlockNum(s.BlockNumber-unwind, tx) + minUnwindableBlockNum, _, err := libstate.AggTx(tx).CanUnwindBeforeBlockNum(s.BlockNumber-unwind, tx) if err != nil { return err } @@ -1149,45 +1035,18 @@ func stageCustomTrace(db kv.TemporalRwDB, ctx context.Context, logger log.Logger must(sync.SetCurrentStage(stages.Execution)) chainConfig := fromdb.ChainConfig(db) - genesis := core.GenesisBlockByChainName(chain) + genesis := readGenesis(chain) blockReader, _ := blocksIO(db, logger) - cfg := stagedsync.StageCustomTraceCfg(strings.Split(domain, ","), db, dirs, blockReader, chainConfig, engine, genesis, &syncCfg) + cfg := stagedsync.StageCustomTraceCfg(strings.Split(domain, ","), db, dirs, blockReader, chainConfig, engine, genesis, syncCfg) if reset { - tx, err := db.BeginTemporalRw(ctx) - if err != nil { - return err - } - defer tx.Rollback() - var tables []string - if cfg.Produce.ReceiptDomain { - tables = append(tables, db.Debug().DomainTables(kv.ReceiptDomain)...) - } - if cfg.Produce.RCacheDomain { - tables = append(tables, db.Debug().DomainTables(kv.RCacheDomain)...) - } - if cfg.Produce.LogAddr { - tables = append(tables, db.Debug().InvertedIdxTables(kv.LogAddrIdx)...) - } - if cfg.Produce.LogTopic { - tables = append(tables, db.Debug().InvertedIdxTables(kv.LogTopicIdx)...) - } - if cfg.Produce.TraceFrom { - tables = append(tables, db.Debug().InvertedIdxTables(kv.TracesFromIdx)...) - } - if cfg.Produce.TraceTo { - tables = append(tables, db.Debug().InvertedIdxTables(kv.TracesToIdx)...) - } - if err := backup.ClearTables(ctx, tx, tables...); err != nil { - return err - } - if err := tx.Commit(); err != nil { + if err := stagedsync.StageCustomTraceReset(ctx, db, cfg.Produce); err != nil { return err } - if err := reset2.Reset(ctx, db, stages.CustomTrace); err != nil { - return err - } + // if err := reset2.Reset(ctx, db, stages.CustomTrace); err != nil { + // return err + // } return nil } @@ -1368,7 +1227,7 @@ func allSnapshots(ctx context.Context, db kv.RwDB, logger log.Logger) (*freezebl _heimdallStoreSingleton = heimdall.NewSnapshotStore(heimdall.NewDbStore(db), _allBorSnapshotsSingleton) _allBscSnapshotsSingleton = freezeblocks.NewBscRoSnapshots(snapCfg, dirs.Snap, 0, logger) blockReader := freezeblocks.NewBlockReader(_allSnapshotsSingleton, _allBorSnapshotsSingleton, _heimdallStoreSingleton, _bridgeStoreSingleton, _allBscSnapshotsSingleton) - txNums := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, blockReader)) + txNums := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)) _aggSingleton, err = libstate.NewAggregator2(ctx, dirs, config3.DefaultStepSize, db, logger) if err != nil { @@ -1435,7 +1294,7 @@ func allSnapshots(ctx context.Context, db kv.RwDB, logger log.Logger) (*freezebl defer tx.Rollback() stats.LogStats(tx, log.New(), func(endTxNumMinimax uint64) (uint64, error) { - _, histBlockNumProgress, err := txNums.FindBlockNum(tx, endTxNumMinimax) + histBlockNumProgress, _, err := txNums.FindBlockNum(tx, endTxNumMinimax) if err != nil { return histBlockNumProgress, fmt.Errorf("findBlockNum(%d) fails: %w", endTxNumMinimax, err) } @@ -1477,7 +1336,7 @@ func newSync(ctx context.Context, db kv.TemporalRwDB, miningConfig *params.Minin events := shards.NewEvents() - genesis := core.GenesisBlockByChainName(chain) + genesis := readGenesis(chain) chainConfig, genesisBlock, genesisErr := core.CommitGenesisBlock(db, genesis, dirs, logger) if _, ok := genesisErr.(*chain2.ConfigCompatError); genesisErr != nil && !ok { panic(genesisErr) @@ -1583,7 +1442,6 @@ func newSync(ctx context.Context, db kv.TemporalRwDB, miningConfig *params.Minin cfg.Sync, stagedsync.MiningStages(ctx, stagedsync.StageMiningCreateBlockCfg(db, miner, *chainConfig, engine, nil, dirs.Tmp, blockReader), - stagedsync.StageBorHeimdallCfg(db, snapDb, miner, *chainConfig, heimdallClient, heimdallStore, bridgeStore, blockReader, nil, nil, recents, signatures, false, unwindTypes), stagedsync.StageExecuteBlocksCfg( db, cfg.Prune, @@ -1605,6 +1463,7 @@ func newSync(ctx context.Context, db kv.TemporalRwDB, miningConfig *params.Minin stagedsync.StageSendersCfg(db, sentryControlServer.ChainConfig, cfg.Sync, false, dirs.Tmp, cfg.Prune, blockReader, sentryControlServer.Hd), stagedsync.StageMiningExecCfg(db, miner, events, *chainConfig, engine, &vm.Config{}, dirs.Tmp, nil, 0, nil, blockReader), stagedsync.StageMiningFinishCfg(db, *chainConfig, engine, miner, miningCancel, blockReader, builder.NewLatestBlockBuiltStore()), + false, ), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder, @@ -1645,7 +1504,7 @@ func initConsensusEngine(ctx context.Context, cc *chain2.Config, dir string, db consensusConfig = cc.Bor config.HeimdallURL = HeimdallURL if !config.WithoutHeimdall { - heimdallClient = heimdall.NewHttpClient(config.HeimdallURL, logger) + heimdallClient = heimdall.NewHttpClient(config.HeimdallURL, logger, heimdall.WithApiVersioner(ctx)) } } else { consensusConfig = &config.Ethash @@ -1653,3 +1512,12 @@ func initConsensusEngine(ctx context.Context, cc *chain2.Config, dir string, db return ethconsensusconfig.CreateConsensusEngine(ctx, &nodecfg.Config{Dirs: datadir.New(dir)}, cc, consensusConfig, config.Miner.Notify, config.Miner.Noverify, heimdallClient, config.WithoutHeimdall, config.DisableBlobPrune, blockReader, db.ReadOnly(), logger, nil, nil), heimdallClient } + +func readGenesis(chain string) *types.Genesis { + genesis := core.GenesisBlockByChainName(chain) + if genesis == nil { + panic("genesis is nil. probably you passed wrong --chain") + } + _ = genesis.Alloc // nil check + return genesis +} diff --git a/cmd/integration/commands/state_domains.go b/cmd/integration/commands/state_domains.go index 68c0e1e79d8..e5b72cc07a3 100644 --- a/cmd/integration/commands/state_domains.go +++ b/cmd/integration/commands/state_domains.go @@ -281,7 +281,7 @@ func makePurifiableIndexDB(db kv.RwDB, dirs datadir.Dirs, logger log.Logger, dom } count++ //fmt.Println("count: ", count, "keyLength: ", len(buf)) - if count%100000 == 0 { + if count%1_000_000 == 0 { fmt.Printf("Indexed %d keys in file %s\n", count, fileName) } // skip values @@ -426,7 +426,7 @@ func makePurifiedDomains(db kv.RwDB, dirs datadir.Dirs, logger log.Logger, domai return fmt.Errorf("failed to add val %x: %w", bufVal, err) } count++ - if count%100000 == 0 { + if count%10_000_000 == 0 { skipRatio := float64(skipped) / float64(count) fmt.Printf("Indexed %d keys, skipped %d, in file %s. skip ratio: %.2f\n", count, skipped, fileName, skipRatio) } diff --git a/cmd/rpcdaemon/cli/config.go b/cmd/rpcdaemon/cli/config.go index 52766c7c9b9..74b1bde282c 100644 --- a/cmd/rpcdaemon/cli/config.go +++ b/cmd/rpcdaemon/cli/config.go @@ -461,7 +461,7 @@ func RemoteServices(ctx context.Context, cfg *httpcfg.HttpCfg, logger log.Logger } blockReader = freezeblocks.NewBlockReader(allSnapshots, allBorSnapshots, heimdallStore, bridgeStore, allBscSnapshots) - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)) agg, err := libstate.NewAggregator2(ctx, cfg.Dirs, config3.DefaultStepSize, rawDB, logger) if err != nil { @@ -527,7 +527,7 @@ func RemoteServices(ctx context.Context, cfg *httpcfg.HttpCfg, logger log.Logger } defer tx.Rollback() stats.LogStats(tx, logger, func(endTxNumMinimax uint64) (uint64, error) { - _, histBlockNumProgress, err := txNumsReader.FindBlockNum(tx, endTxNumMinimax) + histBlockNumProgress, _, err := txNumsReader.FindBlockNum(tx, endTxNumMinimax) return histBlockNumProgress, err }) } @@ -552,7 +552,7 @@ func RemoteServices(ctx context.Context, cfg *httpcfg.HttpCfg, logger log.Logger defer tx.Rollback() stats.LogStats(tx, logger, func(endTxNumMinimax uint64) (uint64, error) { - _, histBlockNumProgress, err := txNumsReader.FindBlockNum(tx, endTxNumMinimax) + histBlockNumProgress, _, err := txNumsReader.FindBlockNum(tx, endTxNumMinimax) return histBlockNumProgress, err }) } diff --git a/cmd/rpctest/main.go b/cmd/rpctest/main.go index 542c49144b4..eec624b331e 100644 --- a/cmd/rpctest/main.go +++ b/cmd/rpctest/main.go @@ -266,7 +266,7 @@ func main() { Short: "", Long: ``, Run: func(cmd *cobra.Command, args []string) { - err := rpctest.EthGetLogsInvariants(erigonURL, gethURL, needCompare, blockFrom, blockTo) + err := rpctest.EthGetLogsInvariants(cmd.Context(), erigonURL, gethURL, needCompare, blockFrom, blockTo) if err != nil { logger.Error(err.Error()) } diff --git a/cmd/rpctest/rpctest/bench_ethgetlogs.go b/cmd/rpctest/rpctest/bench_ethgetlogs.go index ca473857f98..abe2b1fdeae 100644 --- a/cmd/rpctest/rpctest/bench_ethgetlogs.go +++ b/cmd/rpctest/rpctest/bench_ethgetlogs.go @@ -18,14 +18,16 @@ package rpctest import ( "bufio" + "context" "fmt" "math/rand" "os" - "runtime" + "slices" "time" "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon/eth/ethconfig/estimate" "golang.org/x/sync/errgroup" ) @@ -142,7 +144,7 @@ func BenchEthGetLogs(erigonURL, gethURL string, needCompare bool, blockFrom uint return nil } -func EthGetLogsInvariants(erigonURL, gethURL string, needCompare bool, blockFrom, blockTo uint64) error { +func EthGetLogsInvariants(ctx context.Context, erigonURL, gethURL string, needCompare bool, blockFrom, blockTo uint64) error { setRoutes(erigonURL, gethURL) reqGen := &RequestGenerator{} @@ -159,97 +161,103 @@ func EthGetLogsInvariants(erigonURL, gethURL string, needCompare bool, blockFrom logEvery := time.NewTicker(20 * time.Second) defer logEvery.Stop() - noDuplicates := func(logs []Log, blockNum uint64, addr common.Address) error { + noDuplicates := func(logs []Log) error { if len(logs) <= 1 { return nil } - //for i := 1; i < len(logs); i++ { - // if logs[i-1].TxIndex == logs[i].TxIndex { - // return fmt.Errorf("eth_getLogs: at blockNum=%d and addr %x has duplicated logs", blockNum, addr) - // } - //} + var indices []uint64 + for i := 0; i < len(logs); i++ { + indices = append(indices, uint64(logs[i].Index)) + } + slices.Sort(indices) + for i := 1; i < len(logs); i++ { + if indices[i-1] == indices[i] { + return fmt.Errorf("duplicated log_index %d", indices[i]) + } + } return nil } - _prevBn := blockFrom for bn := blockFrom; bn < blockTo; { - batchEnd := min(bn+1000, blockTo) + batchEnd := min(bn+10, blockTo) eg := &errgroup.Group{} - eg.SetLimit(runtime.NumCPU()) + eg.SetLimit(estimate.AlmostAllCPUs()) + //eg.SetLimit(1) for ; bn < batchEnd; bn++ { bn := bn - prevBn := _prevBn - eg.Go(func() error { - var resp EthGetLogs - res := reqGen.Erigon("eth_getLogs", reqGen.getLogsNoFilters(prevBn, bn), &resp) + //eg.Go(func() error { + var resp EthGetLogs + res := reqGen.Erigon("eth_getLogs", reqGen.getLogsNoFilters(bn, bn), &resp) + if res.Err != nil { + return fmt.Errorf("Could not get modified accounts (Erigon): %v\n", res.Err) + } + if resp.Error != nil { + return fmt.Errorf("Error getting modified accounts (Erigon): %d %s\n", resp.Error.Code, resp.Error.Message) + } + if err := noDuplicates(resp.Result); err != nil { + return fmt.Errorf("eth_getLogs: at blockNum=%d %w", bn, err) + } + + sawAddr := map[common.Address]struct{}{} // don't check same addr in this block + sawTopic := map[common.Hash]struct{}{} + for _, l := range resp.Result { + if _, ok := sawAddr[l.Address]; ok { + continue + } + sawAddr[l.Address] = struct{}{} + + res = reqGen.Erigon("eth_getLogs", reqGen.getLogs(bn, bn, l.Address), &resp) if res.Err != nil { return fmt.Errorf("Could not get modified accounts (Erigon): %v\n", res.Err) } if resp.Error != nil { return fmt.Errorf("Error getting modified accounts (Erigon): %d %s\n", resp.Error.Code, resp.Error.Message) } + //invariant1: if `log` visible without filter - then must be visible with filter. (in another words: `address` must be indexed well) + if len(resp.Result) == 0 { + return fmt.Errorf("eth_getLogs: at blockNum=%d account %x not indexed", bn, l.Address) + } - sawAddr := map[common.Address]struct{}{} // don't check same addr in this block - sawTopic := map[common.Hash]struct{}{} - for _, l := range resp.Result { - if _, ok := sawAddr[l.Address]; ok { - continue - } - sawAddr[l.Address] = struct{}{} - - res = reqGen.Erigon("eth_getLogs", reqGen.getLogs(prevBn, bn, l.Address), &resp) - if res.Err != nil { - return fmt.Errorf("Could not get modified accounts (Erigon): %v\n", res.Err) - } - if resp.Error != nil { - return fmt.Errorf("Error getting modified accounts (Erigon): %d %s\n", resp.Error.Code, resp.Error.Message) - } - //invariant1.1: if `log` visible without filter - then must be visible with filter. (in another words: `address` must be indexed well) - if len(resp.Result) == 0 { - return fmt.Errorf("eth_getLogs: at blockNum=%d account %x not indexed", bn, l.Address) - } - - //invariant1.2: no repeats - if err := noDuplicates(resp.Result, bn, l.Address); err != nil { - return err - } + if err := noDuplicates(resp.Result); err != nil { + return fmt.Errorf("eth_getLogs: at blockNum=%d and addr %x %w", bn, l.Address, err) + } - //invariant2.1: if `log` visible without filter - then must be visible with filter. (in another words: `topic` must be indexed well) - if len(l.Topics) == 0 { - continue - } + //invariant2: if `log` visible without filter - then must be visible with filter. (in another words: `topic` must be indexed well) + if len(l.Topics) == 0 { + continue + } - if _, ok := sawTopic[l.Topics[0]]; ok { - continue - } + if _, ok := sawTopic[l.Topics[0]]; ok { + continue + } - sawTopic[l.Topics[0]] = struct{}{} + sawTopic[l.Topics[0]] = struct{}{} - res = reqGen.Erigon("eth_getLogs", reqGen.getLogs1(prevBn, bn, l.Address, l.Topics[0]), &resp) - if res.Err != nil { - return fmt.Errorf("Could not get modified accounts (Erigon): %v\n", res.Err) - } - if resp.Error != nil { - return fmt.Errorf("Error getting modified accounts (Erigon): %d %s\n", resp.Error.Code, resp.Error.Message) - } - if len(resp.Result) == 0 { - return fmt.Errorf("eth_getLogs: at blockNum=%d account %x, topic %x not indexed", bn, l.Address, l.Topics[0]) - } - //invariant2.2: no repeats - if err := noDuplicates(resp.Result, bn, l.Address); err != nil { - return err - } + res = reqGen.Erigon("eth_getLogs", reqGen.getLogs1(bn, bn, l.Address, l.Topics[0]), &resp) + if res.Err != nil { + return fmt.Errorf("Could not get modified accounts (Erigon): %v\n", res.Err) } - - select { - case <-logEvery.C: - log.Info("[ethGetLogsInvariants]", "block_num", bn) - default: + if resp.Error != nil { + return fmt.Errorf("Error getting modified accounts (Erigon): %d %s\n", resp.Error.Code, resp.Error.Message) + } + if len(resp.Result) == 0 { + return fmt.Errorf("eth_getLogs: at blockNum=%d account %x, topic %x not indexed", bn, l.Address, l.Topics[0]) } + if err := noDuplicates(resp.Result); err != nil { + return fmt.Errorf("eth_getLogs: at blockNum=%d and topic %x %w", bn, l.Topics[0], err) + } + } + + select { + case <-logEvery.C: + log.Info("[ethGetLogsInvariants]", "block_num", bn) + case <-ctx.Done(): + return ctx.Err() + default: + } - return nil - }) - _prevBn = bn + //return nil + //}) } if err := eg.Wait(); err != nil { diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go index a3bff6a01bd..430bdb6ae13 100644 --- a/cmd/state/commands/opcode_tracer.go +++ b/cmd/state/commands/opcode_tracer.go @@ -582,6 +582,7 @@ func OpcodeTracer(genesis *types.Genesis, blockNum uint64, chaindata string, num timeLastBlock := startTime blockNumLastReport := blockNum + txNumReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(context.Background(), blockReader)) for !interrupt { var block *types.Block @@ -605,7 +606,7 @@ func OpcodeTracer(genesis *types.Genesis, blockNum uint64, chaindata string, num ot.fsumWriter = bufio.NewWriter(fsum) } - dbstate, err := rpchelper.CreateHistoryStateReader(historyTx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(context.TODO(), blockReader)), + dbstate, err := rpchelper.CreateHistoryStateReader(historyTx, txNumReader, block.NumberU64(), 0, chainConfig.ChainName) if err != nil { return err diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ad951a1ea88..ddbb5c6f32d 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -237,7 +237,6 @@ var ( MinerGasLimitFlag = cli.Uint64Flag{ Name: "miner.gaslimit", Usage: "Target gas limit for mined blocks", - Value: ethconfig.Defaults.Miner.GasLimit, } MinerGasPriceFlag = flags.BigFlag{ Name: "miner.gasprice", @@ -1676,9 +1675,12 @@ func SetupMinerCobra(cmd *cobra.Command, cfg *params.MiningConfig) { panic(err) } cfg.ExtraData = []byte(extraDataStr) - cfg.GasLimit, err = flags.GetUint64(MinerGasLimitFlag.Name) - if err != nil { - panic(err) + if flags.Changed(MinerGasLimitFlag.Name) { + gasLimit, err := flags.GetUint64(MinerGasLimitFlag.Name) + if err != nil { + panic(err) + } + cfg.GasLimit = &gasLimit } price, err := flags.GetInt64(MinerGasPriceFlag.Name) if err != nil { @@ -1753,7 +1755,7 @@ func setBorConfig(ctx *cli.Context, cfg *ethconfig.Config, nodeConfig *nodecfg.C } func setMiner(ctx *cli.Context, cfg *params.MiningConfig) { - cfg.Enabled = ctx.IsSet(MiningEnabledFlag.Name) + cfg.Enabled = ctx.Bool(MiningEnabledFlag.Name) cfg.EnabledPOS = !ctx.IsSet(ProposingDisableFlag.Name) if cfg.Enabled && len(cfg.Etherbase.Bytes()) == 0 { @@ -1775,7 +1777,9 @@ func setMiner(ctx *cli.Context, cfg *params.MiningConfig) { } if ctx.IsSet(MinerGasLimitFlag.Name) { - cfg.GasLimit = ctx.Uint64(MinerGasLimitFlag.Name) + if gasLimit := ctx.Uint64(MinerGasLimitFlag.Name); gasLimit != 0 { + cfg.GasLimit = &gasLimit + } } if ctx.IsSet(MinerGasPriceFlag.Name) { cfg.GasPrice = flags.GlobalBig(ctx, MinerGasPriceFlag.Name) diff --git a/consensus/chain_reader_mock.go b/consensus/chain_reader_mock.go new file mode 100644 index 00000000000..a9ea8307593 --- /dev/null +++ b/consensus/chain_reader_mock.go @@ -0,0 +1,577 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/erigontech/erigon/consensus (interfaces: ChainReader) +// +// Generated by this command: +// +// mockgen -typed=true -destination=./chain_reader_mock.go -package=consensus . ChainReader +// + +// Package consensus is a generated GoMock package. +package consensus + +import ( + big "math/big" + reflect "reflect" + + chain "github.com/erigontech/erigon-lib/chain" + common "github.com/erigontech/erigon-lib/common" + rlp "github.com/erigontech/erigon-lib/rlp" + types "github.com/erigontech/erigon/core/types" + gomock "go.uber.org/mock/gomock" +) + +// MockChainReader is a mock of ChainReader interface. +type MockChainReader struct { + ctrl *gomock.Controller + recorder *MockChainReaderMockRecorder + isgomock struct{} +} + +// MockChainReaderMockRecorder is the mock recorder for MockChainReader. +type MockChainReaderMockRecorder struct { + mock *MockChainReader +} + +// NewMockChainReader creates a new mock instance. +func NewMockChainReader(ctrl *gomock.Controller) *MockChainReader { + mock := &MockChainReader{ctrl: ctrl} + mock.recorder = &MockChainReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChainReader) EXPECT() *MockChainReaderMockRecorder { + return m.recorder +} + +// BorEventsByBlock mocks base method. +func (m *MockChainReader) BorEventsByBlock(hash common.Hash, number uint64) []rlp.RawValue { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BorEventsByBlock", hash, number) + ret0, _ := ret[0].([]rlp.RawValue) + return ret0 +} + +// BorEventsByBlock indicates an expected call of BorEventsByBlock. +func (mr *MockChainReaderMockRecorder) BorEventsByBlock(hash, number any) *MockChainReaderBorEventsByBlockCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BorEventsByBlock", reflect.TypeOf((*MockChainReader)(nil).BorEventsByBlock), hash, number) + return &MockChainReaderBorEventsByBlockCall{Call: call} +} + +// MockChainReaderBorEventsByBlockCall wrap *gomock.Call +type MockChainReaderBorEventsByBlockCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderBorEventsByBlockCall) Return(arg0 []rlp.RawValue) *MockChainReaderBorEventsByBlockCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderBorEventsByBlockCall) Do(f func(common.Hash, uint64) []rlp.RawValue) *MockChainReaderBorEventsByBlockCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderBorEventsByBlockCall) DoAndReturn(f func(common.Hash, uint64) []rlp.RawValue) *MockChainReaderBorEventsByBlockCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// BorStartEventId mocks base method. +func (m *MockChainReader) BorStartEventId(hash common.Hash, number uint64) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BorStartEventId", hash, number) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// BorStartEventId indicates an expected call of BorStartEventId. +func (mr *MockChainReaderMockRecorder) BorStartEventId(hash, number any) *MockChainReaderBorStartEventIdCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BorStartEventId", reflect.TypeOf((*MockChainReader)(nil).BorStartEventId), hash, number) + return &MockChainReaderBorStartEventIdCall{Call: call} +} + +// MockChainReaderBorStartEventIdCall wrap *gomock.Call +type MockChainReaderBorStartEventIdCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderBorStartEventIdCall) Return(arg0 uint64) *MockChainReaderBorStartEventIdCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderBorStartEventIdCall) Do(f func(common.Hash, uint64) uint64) *MockChainReaderBorStartEventIdCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderBorStartEventIdCall) DoAndReturn(f func(common.Hash, uint64) uint64) *MockChainReaderBorStartEventIdCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Config mocks base method. +func (m *MockChainReader) Config() *chain.Config { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Config") + ret0, _ := ret[0].(*chain.Config) + return ret0 +} + +// Config indicates an expected call of Config. +func (mr *MockChainReaderMockRecorder) Config() *MockChainReaderConfigCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Config", reflect.TypeOf((*MockChainReader)(nil).Config)) + return &MockChainReaderConfigCall{Call: call} +} + +// MockChainReaderConfigCall wrap *gomock.Call +type MockChainReaderConfigCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderConfigCall) Return(arg0 *chain.Config) *MockChainReaderConfigCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderConfigCall) Do(f func() *chain.Config) *MockChainReaderConfigCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderConfigCall) DoAndReturn(f func() *chain.Config) *MockChainReaderConfigCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CurrentFinalizedHeader mocks base method. +func (m *MockChainReader) CurrentFinalizedHeader() *types.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CurrentFinalizedHeader") + ret0, _ := ret[0].(*types.Header) + return ret0 +} + +// CurrentFinalizedHeader indicates an expected call of CurrentFinalizedHeader. +func (mr *MockChainReaderMockRecorder) CurrentFinalizedHeader() *MockChainReaderCurrentFinalizedHeaderCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentFinalizedHeader", reflect.TypeOf((*MockChainReader)(nil).CurrentFinalizedHeader)) + return &MockChainReaderCurrentFinalizedHeaderCall{Call: call} +} + +// MockChainReaderCurrentFinalizedHeaderCall wrap *gomock.Call +type MockChainReaderCurrentFinalizedHeaderCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderCurrentFinalizedHeaderCall) Return(arg0 *types.Header) *MockChainReaderCurrentFinalizedHeaderCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderCurrentFinalizedHeaderCall) Do(f func() *types.Header) *MockChainReaderCurrentFinalizedHeaderCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderCurrentFinalizedHeaderCall) DoAndReturn(f func() *types.Header) *MockChainReaderCurrentFinalizedHeaderCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CurrentHeader mocks base method. +func (m *MockChainReader) CurrentHeader() *types.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CurrentHeader") + ret0, _ := ret[0].(*types.Header) + return ret0 +} + +// CurrentHeader indicates an expected call of CurrentHeader. +func (mr *MockChainReaderMockRecorder) CurrentHeader() *MockChainReaderCurrentHeaderCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentHeader", reflect.TypeOf((*MockChainReader)(nil).CurrentHeader)) + return &MockChainReaderCurrentHeaderCall{Call: call} +} + +// MockChainReaderCurrentHeaderCall wrap *gomock.Call +type MockChainReaderCurrentHeaderCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderCurrentHeaderCall) Return(arg0 *types.Header) *MockChainReaderCurrentHeaderCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderCurrentHeaderCall) Do(f func() *types.Header) *MockChainReaderCurrentHeaderCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderCurrentHeaderCall) DoAndReturn(f func() *types.Header) *MockChainReaderCurrentHeaderCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// CurrentSafeHeader mocks base method. +func (m *MockChainReader) CurrentSafeHeader() *types.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CurrentSafeHeader") + ret0, _ := ret[0].(*types.Header) + return ret0 +} + +// CurrentSafeHeader indicates an expected call of CurrentSafeHeader. +func (mr *MockChainReaderMockRecorder) CurrentSafeHeader() *MockChainReaderCurrentSafeHeaderCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentSafeHeader", reflect.TypeOf((*MockChainReader)(nil).CurrentSafeHeader)) + return &MockChainReaderCurrentSafeHeaderCall{Call: call} +} + +// MockChainReaderCurrentSafeHeaderCall wrap *gomock.Call +type MockChainReaderCurrentSafeHeaderCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderCurrentSafeHeaderCall) Return(arg0 *types.Header) *MockChainReaderCurrentSafeHeaderCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderCurrentSafeHeaderCall) Do(f func() *types.Header) *MockChainReaderCurrentSafeHeaderCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderCurrentSafeHeaderCall) DoAndReturn(f func() *types.Header) *MockChainReaderCurrentSafeHeaderCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// FrozenBlocks mocks base method. +func (m *MockChainReader) FrozenBlocks() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FrozenBlocks") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// FrozenBlocks indicates an expected call of FrozenBlocks. +func (mr *MockChainReaderMockRecorder) FrozenBlocks() *MockChainReaderFrozenBlocksCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FrozenBlocks", reflect.TypeOf((*MockChainReader)(nil).FrozenBlocks)) + return &MockChainReaderFrozenBlocksCall{Call: call} +} + +// MockChainReaderFrozenBlocksCall wrap *gomock.Call +type MockChainReaderFrozenBlocksCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderFrozenBlocksCall) Return(arg0 uint64) *MockChainReaderFrozenBlocksCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderFrozenBlocksCall) Do(f func() uint64) *MockChainReaderFrozenBlocksCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderFrozenBlocksCall) DoAndReturn(f func() uint64) *MockChainReaderFrozenBlocksCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// FrozenBorBlocks mocks base method. +func (m *MockChainReader) FrozenBorBlocks() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FrozenBorBlocks") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// FrozenBorBlocks indicates an expected call of FrozenBorBlocks. +func (mr *MockChainReaderMockRecorder) FrozenBorBlocks() *MockChainReaderFrozenBorBlocksCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FrozenBorBlocks", reflect.TypeOf((*MockChainReader)(nil).FrozenBorBlocks)) + return &MockChainReaderFrozenBorBlocksCall{Call: call} +} + +// MockChainReaderFrozenBorBlocksCall wrap *gomock.Call +type MockChainReaderFrozenBorBlocksCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderFrozenBorBlocksCall) Return(arg0 uint64) *MockChainReaderFrozenBorBlocksCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderFrozenBorBlocksCall) Do(f func() uint64) *MockChainReaderFrozenBorBlocksCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderFrozenBorBlocksCall) DoAndReturn(f func() uint64) *MockChainReaderFrozenBorBlocksCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetBlock mocks base method. +func (m *MockChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlock", hash, number) + ret0, _ := ret[0].(*types.Block) + return ret0 +} + +// GetBlock indicates an expected call of GetBlock. +func (mr *MockChainReaderMockRecorder) GetBlock(hash, number any) *MockChainReaderGetBlockCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockChainReader)(nil).GetBlock), hash, number) + return &MockChainReaderGetBlockCall{Call: call} +} + +// MockChainReaderGetBlockCall wrap *gomock.Call +type MockChainReaderGetBlockCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderGetBlockCall) Return(arg0 *types.Block) *MockChainReaderGetBlockCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderGetBlockCall) Do(f func(common.Hash, uint64) *types.Block) *MockChainReaderGetBlockCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderGetBlockCall) DoAndReturn(f func(common.Hash, uint64) *types.Block) *MockChainReaderGetBlockCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetHeader mocks base method. +func (m *MockChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHeader", hash, number) + ret0, _ := ret[0].(*types.Header) + return ret0 +} + +// GetHeader indicates an expected call of GetHeader. +func (mr *MockChainReaderMockRecorder) GetHeader(hash, number any) *MockChainReaderGetHeaderCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeader", reflect.TypeOf((*MockChainReader)(nil).GetHeader), hash, number) + return &MockChainReaderGetHeaderCall{Call: call} +} + +// MockChainReaderGetHeaderCall wrap *gomock.Call +type MockChainReaderGetHeaderCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderGetHeaderCall) Return(arg0 *types.Header) *MockChainReaderGetHeaderCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderGetHeaderCall) Do(f func(common.Hash, uint64) *types.Header) *MockChainReaderGetHeaderCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderGetHeaderCall) DoAndReturn(f func(common.Hash, uint64) *types.Header) *MockChainReaderGetHeaderCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetHeaderByHash mocks base method. +func (m *MockChainReader) GetHeaderByHash(hash common.Hash) *types.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHeaderByHash", hash) + ret0, _ := ret[0].(*types.Header) + return ret0 +} + +// GetHeaderByHash indicates an expected call of GetHeaderByHash. +func (mr *MockChainReaderMockRecorder) GetHeaderByHash(hash any) *MockChainReaderGetHeaderByHashCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeaderByHash", reflect.TypeOf((*MockChainReader)(nil).GetHeaderByHash), hash) + return &MockChainReaderGetHeaderByHashCall{Call: call} +} + +// MockChainReaderGetHeaderByHashCall wrap *gomock.Call +type MockChainReaderGetHeaderByHashCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderGetHeaderByHashCall) Return(arg0 *types.Header) *MockChainReaderGetHeaderByHashCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderGetHeaderByHashCall) Do(f func(common.Hash) *types.Header) *MockChainReaderGetHeaderByHashCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderGetHeaderByHashCall) DoAndReturn(f func(common.Hash) *types.Header) *MockChainReaderGetHeaderByHashCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetHeaderByNumber mocks base method. +func (m *MockChainReader) GetHeaderByNumber(number uint64) *types.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHeaderByNumber", number) + ret0, _ := ret[0].(*types.Header) + return ret0 +} + +// GetHeaderByNumber indicates an expected call of GetHeaderByNumber. +func (mr *MockChainReaderMockRecorder) GetHeaderByNumber(number any) *MockChainReaderGetHeaderByNumberCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHeaderByNumber", reflect.TypeOf((*MockChainReader)(nil).GetHeaderByNumber), number) + return &MockChainReaderGetHeaderByNumberCall{Call: call} +} + +// MockChainReaderGetHeaderByNumberCall wrap *gomock.Call +type MockChainReaderGetHeaderByNumberCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderGetHeaderByNumberCall) Return(arg0 *types.Header) *MockChainReaderGetHeaderByNumberCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderGetHeaderByNumberCall) Do(f func(uint64) *types.Header) *MockChainReaderGetHeaderByNumberCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderGetHeaderByNumberCall) DoAndReturn(f func(uint64) *types.Header) *MockChainReaderGetHeaderByNumberCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// GetTd mocks base method. +func (m *MockChainReader) GetTd(hash common.Hash, number uint64) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTd", hash, number) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetTd indicates an expected call of GetTd. +func (mr *MockChainReaderMockRecorder) GetTd(hash, number any) *MockChainReaderGetTdCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTd", reflect.TypeOf((*MockChainReader)(nil).GetTd), hash, number) + return &MockChainReaderGetTdCall{Call: call} +} + +// MockChainReaderGetTdCall wrap *gomock.Call +type MockChainReaderGetTdCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderGetTdCall) Return(arg0 *big.Int) *MockChainReaderGetTdCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderGetTdCall) Do(f func(common.Hash, uint64) *big.Int) *MockChainReaderGetTdCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderGetTdCall) DoAndReturn(f func(common.Hash, uint64) *big.Int) *MockChainReaderGetTdCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// HasBlock mocks base method. +func (m *MockChainReader) HasBlock(hash common.Hash, number uint64) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasBlock", hash, number) + ret0, _ := ret[0].(bool) + return ret0 +} + +// HasBlock indicates an expected call of HasBlock. +func (mr *MockChainReaderMockRecorder) HasBlock(hash, number any) *MockChainReaderHasBlockCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasBlock", reflect.TypeOf((*MockChainReader)(nil).HasBlock), hash, number) + return &MockChainReaderHasBlockCall{Call: call} +} + +// MockChainReaderHasBlockCall wrap *gomock.Call +type MockChainReaderHasBlockCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockChainReaderHasBlockCall) Return(arg0 bool) *MockChainReaderHasBlockCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockChainReaderHasBlockCall) Do(f func(common.Hash, uint64) bool) *MockChainReaderHasBlockCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockChainReaderHasBlockCall) DoAndReturn(f func(common.Hash, uint64) bool) *MockChainReaderHasBlockCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559.go index adeaac10e99..3193f364e54 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559.go @@ -150,8 +150,13 @@ func CalcBaseFee(config *chain.Config, parent *types.Header) *big.Int { func getBaseFeeChangeDenominator(borConfig chain.BorConfig, number uint64) uint64 { // If we're running bor based chain post delhi hardfork, return the new value - if borConfig, ok := borConfig.(*borcfg.BorConfig); ok && borConfig.IsDelhi(number) { - return params.BaseFeeChangeDenominatorPostDelhi + if borConfig, ok := borConfig.(*borcfg.BorConfig); ok { + switch { + case borConfig.IsBhilai(number): + return params.BaseFeeChangeDenominatorPostBhilai + case borConfig.IsDelhi(number): + return params.BaseFeeChangeDenominatorPostDelhi + } } // Return the original once for other chains and pre-fork cases diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 954902a9064..7a5f69d889c 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -254,6 +254,9 @@ func GatherForks(config *chain.Config, genesisTime uint64) (heightForks []uint64 if config.Bor.GetNapoliBlock() != nil { heightForks = append(heightForks, config.Bor.GetNapoliBlock().Uint64()) } + if config.Bor.GetBhilaiBlock() != nil { + heightForks = append(heightForks, config.Bor.GetBhilaiBlock().Uint64()) + } } // Sort the fork block numbers & times to permit chronological XOR diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 0b27309468e..dc988274067 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -1269,7 +1269,7 @@ func WriteDBCommitmentHistoryEnabled(tx kv.RwTx, enabled bool) error { func ReadReceiptCacheV2(tx kv.TemporalTx, blockNum uint64, blockHash common.Hash, txNum uint64, txnHash common.Hash) (*types.Receipt, bool, error) { v, ok, err := tx.HistorySeek(kv.RCacheDomain, receiptCacheKey, txNum+1 /*history storing values BEFORE-change*/) if err != nil { - return nil, false, fmt.Errorf("unexpected error, couldn't find changeset: txNum=%d, %w", txNum, err) + return nil, false, err } if !ok { return nil, false, nil @@ -1304,7 +1304,7 @@ func ReadReceiptsCacheV2(tx kv.TemporalTx, block *types.Block, txNumReader rawdb for txnID := _min; txnID < _max+1; txnID++ { v, ok, err := tx.HistorySeek(kv.RCacheDomain, receiptCacheKey, txnID+1) if err != nil { - return nil, fmt.Errorf("unexpected error, couldn't find changeset: txNum=%d, %w", txnID, err) + return nil, err } if !ok { continue diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 88e44acb49d..154b02a1c83 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -473,7 +473,7 @@ func TestBlockReceiptStorage(t *testing.T) { require.NoError(t, err) defer tx.Rollback() br := m.BlockReader - txNumReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(context.Background(), br)) + txNumReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(context.Background(), br)) require := require.New(t) ctx := m.Ctx diff --git a/core/rawdb/rawdbreset/reset_stages.go b/core/rawdb/rawdbreset/reset_stages.go index 9192118485c..7da8216da93 100644 --- a/core/rawdb/rawdbreset/reset_stages.go +++ b/core/rawdb/rawdbreset/reset_stages.go @@ -119,9 +119,6 @@ func ResetBorHeimdall(ctx context.Context, tx kv.RwTx, db kv.RwDB) error { if err := tx.ClearBucket(kv.BorSpans); err != nil { return err } - if err := clearStageProgress(tx, stages.BorHeimdall); err != nil { - return err - } if !useExternalTx { return tx.Commit() } @@ -133,6 +130,7 @@ func ResetPolygonSync(tx kv.RwTx, br services.FullBlockReader, bw *blockio.Block kv.BorEventNums, kv.BorEvents, kv.BorSpans, + kv.BorEventTimes, kv.BorEventProcessedBlocks, kv.BorMilestones, kv.BorCheckpoints, diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 013e7555b38..5dff4434dc8 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -57,6 +57,14 @@ func MakeSigner(config *chain.Config, blockNumber uint64, blockTime uint64) *Sig signer.setCode = true signer.chainID.Set(&chainId) signer.chainIDMul.Mul(&chainId, u256.Num2) + case config.IsBhilai(blockNumber): + signer.protected = true + signer.accessList = true + signer.dynamicFee = true + signer.blob = false + signer.setCode = true + signer.chainID.Set(&chainId) + signer.chainIDMul.Mul(&chainId, u256.Num2) case config.IsCancun(blockNumber, blockTime): // All transaction types are still supported signer.protected = true @@ -127,6 +135,9 @@ func LatestSigner(config *chain.Config) *Signer { if config.PragueTime != nil { signer.setCode = true } + if config.Bor != nil && config.Bor.GetBhilaiBlock() != nil { + signer.setCode = true + } } return &signer } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index b7a14e59c1f..7185513d67d 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -201,6 +201,26 @@ var PrecompiledContractsNapoli = map[libcommon.Address]PrecompiledContract{ libcommon.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, } +var PrecompiledContractsBhilai = map[libcommon.Address]PrecompiledContract{ + libcommon.BytesToAddress([]byte{0x01}): &ecrecover{}, + libcommon.BytesToAddress([]byte{0x02}): &sha256hash{}, + libcommon.BytesToAddress([]byte{0x03}): &ripemd160hash{}, + libcommon.BytesToAddress([]byte{0x04}): &dataCopy{}, + libcommon.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true}, + libcommon.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{}, + libcommon.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{}, + libcommon.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{}, + libcommon.BytesToAddress([]byte{0x09}): &blake2F{}, + libcommon.BytesToAddress([]byte{0x0b}): &bls12381G1Add{}, + libcommon.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{}, + libcommon.BytesToAddress([]byte{0x0d}): &bls12381G2Add{}, + libcommon.BytesToAddress([]byte{0x0e}): &bls12381G2MultiExp{}, + libcommon.BytesToAddress([]byte{0x0f}): &bls12381Pairing{}, + libcommon.BytesToAddress([]byte{0x10}): &bls12381MapFpToG1{}, + libcommon.BytesToAddress([]byte{0x11}): &bls12381MapFp2ToG2{}, + libcommon.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{}, +} + var PrecompiledContractsPrague = map[libcommon.Address]PrecompiledContract{ libcommon.BytesToAddress([]byte{0x01}): &ecrecover{}, libcommon.BytesToAddress([]byte{0x02}): &sha256hash{}, @@ -377,6 +397,7 @@ var ( PrecompiledAddressesPrague []libcommon.Address PrecompiledAddressesPragueForBSC []libcommon.Address PrecompiledAddressesNapoli []libcommon.Address + PrecompiledAddressesBhilai []libcommon.Address PrecompiledAddressesCancun []libcommon.Address PrecompiledAddressesCancunForBSC []libcommon.Address PrecompiledAddressesBerlin []libcommon.Address @@ -435,6 +456,9 @@ func init() { for k := range PrecompiledContractsNapoli { PrecompiledAddressesNapoli = append(PrecompiledAddressesNapoli, k) } + for k := range PrecompiledContractsBhilai { + PrecompiledAddressesBhilai = append(PrecompiledAddressesBhilai, k) + } for k := range PrecompiledContractsPrague { PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k) } @@ -446,6 +470,8 @@ func init() { // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules *chain.Rules) []libcommon.Address { switch { + case rules.IsBhilai: + return PrecompiledAddressesBhilai case rules.IsPrague: if rules.IsParlia { return PrecompiledAddressesPragueForBSC diff --git a/core/vm/evm.go b/core/vm/evm.go index 67c5a50dc53..31727ecffae 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -40,6 +40,8 @@ var emptyHash = libcommon.Hash{} func (evm *EVM) precompile(addr libcommon.Address) (PrecompiledContract, bool) { var precompiles map[libcommon.Address]PrecompiledContract switch { + case evm.chainRules.IsBhilai: + precompiles = PrecompiledContractsBhilai case evm.chainRules.IsPrague: if evm.chainRules.IsParlia { precompiles = PrecompiledContractsPragueForBSC @@ -500,22 +502,26 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gasRemainin if err == nil && evm.chainRules.IsLondon && len(ret) >= 1 && ret[0] == 0xEF { err = ErrInvalidCode } - // if the contract creation ran successfully and no errors were returned + // If the contract creation ran successfully and no errors were returned, // calculate the gas required to store the code. If the code could not - // be stored due to not enough gas set an error and let it be handled + // be stored due to not enough gas, set an error when we're in Homestead and let it be handled // by the error checking condition below. if err == nil { createDataGas := uint64(len(ret)) * params.CreateDataGas if contract.UseGas(createDataGas, tracing.GasChangeCallCodeStorage) { evm.intraBlockState.SetCode(address, ret) - } else if evm.chainRules.IsHomestead { - err = ErrCodeStoreOutOfGas + } else { + // If we run out of gas, we do not store the code: the returned code must be empty. + ret = []byte{} + if evm.chainRules.IsHomestead { + err = ErrCodeStoreOutOfGas + } } } // When an error was returned by the EVM or when setting the creation code - // above we revert to the snapshot and consume any gas remaining. Additionally - // when we're in homestead this also counts for code storage gas errors. + // above, we revert to the snapshot and consume any gas remaining. Additionally, + // when we're in Homestead, this also counts for code storage gas errors. if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.intraBlockState.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 845d3ca3fec..c4df30787f8 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -129,6 +129,8 @@ func copyJumpTable(jt *JumpTable) *JumpTable { func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { var jt *JumpTable switch { + case evm.ChainRules().IsBhilai: + jt = &bhilaiInstructionSet case evm.ChainRules().IsPrague: jt = &pragueInstructionSet case evm.ChainRules().IsCancun: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index bd4d84f5100..6ad9621fcf4 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -65,6 +65,7 @@ var ( londonInstructionSet = newLondonInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() napoliInstructionSet = newNapoliInstructionSet() + bhilaiInstructionSet = newBhilaiInstructionSet() cancunInstructionSet = newCancunInstructionSet() pragueInstructionSet = newPragueInstructionSet() ) @@ -111,6 +112,13 @@ func newCancunInstructionSet() JumpTable { return instructionSet } +func newBhilaiInstructionSet() JumpTable { + instructionSet := newNapoliInstructionSet() + enable7702(&instructionSet) // EIP-7702: set code tx + validateAndFillMaxStack(&instructionSet) + return instructionSet +} + func newNapoliInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable1153(&instructionSet) // Transient storage opcodes diff --git a/erigon-lib/chain/chain_config.go b/erigon-lib/chain/chain_config.go index e578ef0d2c4..390463a7aa6 100644 --- a/erigon-lib/chain/chain_config.go +++ b/erigon-lib/chain/chain_config.go @@ -181,6 +181,8 @@ type BorConfig interface { GetNapoliBlock() *big.Int IsAhmedabad(number uint64) bool GetAhmedabadBlock() *big.Int + IsBhilai(num uint64) bool + GetBhilaiBlock() *big.Int StateReceiverContractAddress() common.Address CalculateSprintNumber(number uint64) uint64 CalculateSprintLength(number uint64) uint64 @@ -330,6 +332,11 @@ func (c *Config) IsNapoli(num uint64) bool { return (c != nil) && (c.Bor != nil) && c.Bor.IsNapoli(num) } +// Refer to https://forum.polygon.technology/t/pip-63-bhilai-hardfork +func (c *Config) IsBhilai(num uint64) bool { + return (c != nil) && (c.Bor != nil) && c.Bor.IsBhilai(num) +} + // IsCancun returns whether time is either equal to the Cancun fork time or greater. func (c *Config) IsCancun(num uint64, time uint64) bool { return c.IsLondon(num) && isForked(c.CancunTime, time) @@ -880,7 +887,7 @@ type Rules struct { IsSharding, IsPrague, IsOsaka, IsNapoli bool IsNano, IsMoran, IsGibbs, IsPlanck, IsLuban, IsPlato, IsHertz bool IsHertzfix, IsFeynman, IsFeynmanFix, IsParlia, IsAura bool - IsHaber, IsBohr, IsPascal, IsLorentz, IsMaxwell bool + IsHaber, IsBohr, IsPascal, IsLorentz, IsMaxwell, IsBhilai bool } // Rules ensures c's ChainID is not nil and returns a new Rules instance @@ -920,6 +927,7 @@ func (c *Config) Rules(num uint64, time uint64) *Rules { IsPascal: c.IsPascal(num, time), IsLorentz: c.IsLorentz(num, time), IsMaxwell: c.IsMaxwell(num, time), + IsBhilai: c.IsBhilai(num), IsOsaka: c.IsOsaka(time), IsAura: c.Aura != nil, IsParlia: c.Parlia != nil, diff --git a/erigon-lib/common/datadir/dirs.go b/erigon-lib/common/datadir/dirs.go index 92a4ec62268..51028e18ebd 100644 --- a/erigon-lib/common/datadir/dirs.go +++ b/erigon-lib/common/datadir/dirs.go @@ -19,8 +19,10 @@ package datadir import ( "errors" "fmt" + "io/fs" "os" "path/filepath" + "strings" "syscall" "github.com/gofrs/flock" @@ -199,3 +201,36 @@ func CopyFile(from, to string) error { } return nil } + +func (d Dirs) RenameNewVersions() error { + directories := []string{ + d.Chaindata, d.Tmp, d.SnapIdx, d.SnapHistory, d.SnapDomain, + d.SnapAccessors, d.SnapCaplin, d.Downloader, d.TxPool, d.Snap, + d.Nodes, d.CaplinBlobs, d.CaplinIndexing, d.CaplinLatest, d.CaplinGenesis, + } + + for _, dirPath := range directories { + err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !d.IsDir() && strings.HasPrefix(d.Name(), "v1.0-") { + newName := strings.Replace(d.Name(), "v1.0-", "v1-", 1) + oldPath := path + newPath := filepath.Join(filepath.Dir(path), newName) + + if err := os.Rename(oldPath, newPath); err != nil { + return err + } + } + return nil + }) + + if err != nil { + return err + } + } + + return nil +} diff --git a/erigon-lib/common/dir/rw_dir.go b/erigon-lib/common/dir/rw_dir.go index 87b590e7e28..6733672a837 100644 --- a/erigon-lib/common/dir/rw_dir.go +++ b/erigon-lib/common/dir/rw_dir.go @@ -25,7 +25,10 @@ import ( ) func MustExist(path ...string) { - const perm = 0764 // user rwx, group rw, other r + // user rwx, group rwx, other rx + // x is required to navigate through directories. umask 0o022 is the default and will mask final + // permissions to 0o755 for newly created files (and directories). + const perm = 0o775 for _, p := range path { exist, err := Exist(p) if err != nil { diff --git a/erigon-lib/common/fixedgas/intrinsic_gas.go b/erigon-lib/common/fixedgas/intrinsic_gas.go index 582faa8d2a6..21e1a342fb9 100644 --- a/erigon-lib/common/fixedgas/intrinsic_gas.go +++ b/erigon-lib/common/fixedgas/intrinsic_gas.go @@ -1,3 +1,22 @@ +// Copyright 2014 The go-ethereum Authors +// (original work) +// Copyright 2024 The Erigon Authors +// (modifications) +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + package fixedgas import ( @@ -6,7 +25,7 @@ import ( // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // TODO: convert the input to a struct -func IntrinsicGas(data []byte, accessListLen, storageKeysLen uint64, isContractCreation bool, isHomestead, isEIP2028, isEIP3860, isPrague bool, authorizationsLen uint64) (uint64, uint64, bool) { +func IntrinsicGas(data []byte, accessListLen, storageKeysLen uint64, isContractCreation bool, isEIP2, isEIP2028, isEIP3860, isEIP7623 bool, authorizationsLen uint64) (uint64, uint64, bool) { // Zero and non-zero bytes are priced differently dataLen := uint64(len(data)) dataNonZeroLen := uint64(0) @@ -16,13 +35,13 @@ func IntrinsicGas(data []byte, accessListLen, storageKeysLen uint64, isContractC } } - return CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessListLen, storageKeysLen, isContractCreation, isHomestead, isEIP2028, isEIP3860, isPrague) + return CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessListLen, storageKeysLen, isContractCreation, isEIP2, isEIP2028, isEIP3860, isEIP7623) } // CalcIntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessListLen, storageKeysLen uint64, isContractCreation, isHomestead, isEIP2028, isShanghai, isPrague bool) (gas uint64, floorGas7623 uint64, overflow bool) { +func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessListLen, storageKeysLen uint64, isContractCreation, isEIP2, isEIP2028, isEIP3860, isEIP7623 bool) (gas uint64, floorGas7623 uint64, overflow bool) { // Set the starting gas for the raw transaction - if isContractCreation && isHomestead { + if isContractCreation && isEIP2 { gas = TxGasContractCreation } else { gas = TxGas @@ -58,7 +77,7 @@ func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessListLen, return 0, 0, true } - if isContractCreation && isShanghai { + if isContractCreation && isEIP3860 { numWords := toWordSize(dataLen) product, overflow = math.SafeMul(numWords, InitCodeWordGas) if overflow { @@ -70,8 +89,7 @@ func CalcIntrinsicGas(dataLen, dataNonZeroLen, authorizationsLen, accessListLen, } } - // EIP-7623 - if isPrague { + if isEIP7623 { tokenLen := dataLen + 3*nz dataGas, overflow := math.SafeMul(tokenLen, TxTotalCostFloorPerToken) if overflow { diff --git a/erigon-lib/common/fixedgas/intrinsic_gas_test.go b/erigon-lib/common/fixedgas/intrinsic_gas_test.go index 1509c8fa83b..fdcec91cd20 100644 --- a/erigon-lib/common/fixedgas/intrinsic_gas_test.go +++ b/erigon-lib/common/fixedgas/intrinsic_gas_test.go @@ -1,3 +1,19 @@ +// Copyright 2024 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + package fixedgas import ( diff --git a/erigon-lib/downloader/util.go b/erigon-lib/downloader/util.go index 69fff130f97..da9a0e7abbd 100644 --- a/erigon-lib/downloader/util.go +++ b/erigon-lib/downloader/util.go @@ -543,7 +543,9 @@ func VerifyFileFailFast(ctx context.Context, t *torrent.Torrent, root string, co } good := bytes.Equal(hasher.Sum(nil), p.Hash().Bytes()) if !good { - return fmt.Errorf("hash mismatch at piece %d, file: %s", i, t.Name()) + err := fmt.Errorf("hash mismatch at piece %d, file: %s", i, t.Name()) + log.Warn("[verify.failfast] ", "err", err) + return err } completePieces.Add(1) diff --git a/erigon-lib/kv/mdbx/kv_mdbx.go b/erigon-lib/kv/mdbx/kv_mdbx.go index d60f806b8d0..76a2453b8f3 100644 --- a/erigon-lib/kv/mdbx/kv_mdbx.go +++ b/erigon-lib/kv/mdbx/kv_mdbx.go @@ -373,7 +373,7 @@ func (opts MdbxOpts) Open(ctx context.Context) (kv.RwDB, error) { continue } cnfCopy := db.buckets[name] - dbi, createErr := tx.OpenDBI(name, mdbx.DBAccede, nil, nil) + dbi, createErr := tx.OpenDBISimple(name, mdbx.DBAccede) if createErr != nil { if mdbx.IsNotFound(createErr) { cnfCopy.DBI = NonExistingDBI @@ -776,7 +776,7 @@ func (db *MdbxKV) Update(ctx context.Context, f func(tx kv.RwTx) error) (err err func (tx *MdbxTx) CreateBucket(name string) error { cnfCopy := tx.db.buckets[name] - dbi, err := tx.tx.OpenDBI(name, mdbx.DBAccede, nil, nil) + dbi, err := tx.tx.OpenDBISimple(name, mdbx.DBAccede) if err != nil && !mdbx.IsNotFound(err) { return fmt.Errorf("create table: %s, %w", name, err) } @@ -809,10 +809,10 @@ func (tx *MdbxTx) CreateBucket(name string) error { return errors.New("some not supported flag provided for bucket") } - dbi, err = tx.tx.OpenDBI(name, nativeFlags, nil, nil) + dbi, err = tx.tx.OpenDBISimple(name, nativeFlags) if err != nil { - return fmt.Errorf("db-talbe doesn't exists: %s, lable: %s, %w. Tip: try run `integration run_migrations` to create non-existing tables", name, tx.db.opts.label, err) + return fmt.Errorf("db-table doesn't exists: %s, label: %s, %w. Tip: try run `integration run_migrations` to create non-existing tables", name, tx.db.opts.label, err) } cnfCopy.DBI = kv.DBI(dbi) @@ -825,7 +825,7 @@ func (tx *MdbxTx) dropEvenIfBucketIsNotDeprecated(name string) error { // if bucket was not open on db start, then it's may be deprecated // try to open it now without `Create` flag, and if fail then nothing to drop if dbi == NonExistingDBI { - nativeDBI, err := tx.tx.OpenDBI(name, 0, nil, nil) + nativeDBI, err := tx.tx.OpenDBISimple(name, 0) if err != nil { if mdbx.IsNotFound(err) { return nil // DBI doesn't exists means no drop needed diff --git a/erigon-lib/kv/rawdbv3/txnum.go b/erigon-lib/kv/rawdbv3/txnum.go index 39c6fe0350c..7aac94a55f4 100644 --- a/erigon-lib/kv/rawdbv3/txnum.go +++ b/erigon-lib/kv/rawdbv3/txnum.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "errors" "fmt" + "math" "sort" "github.com/erigontech/erigon-lib/common/dbg" @@ -50,13 +51,26 @@ func (e ErrTxNumsAppendWithGap) Is(err error) bool { return errors.As(err, &target) } -type ReadTxNumFunc func(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint64, ok bool, err error) +type TxBlockIndex interface { + MaxTxNum(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint64, ok bool, err error) + BlockNumber(tx kv.Tx, txNum uint64) (blockNum uint64, ok bool, err error) +} type TxNumsReader struct { - readTxNumFunc ReadTxNumFunc + index TxBlockIndex } -func DefaultReadTxNumFunc(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint64, ok bool, err error) { +// thread safe +type DefaultTxBlockIndex struct{} + +func (d *DefaultTxBlockIndex) MaxTxNum(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint64, ok bool, err error) { + if c == nil { + c, err := tx.Cursor(kv.MaxTxNum) + if err != nil { + return 0, false, err + } + defer c.Close() + } var k [8]byte binary.BigEndian.PutUint64(k[:], blockNum) _, v, err := c.SeekExact(k[:]) @@ -72,13 +86,96 @@ func DefaultReadTxNumFunc(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint return binary.BigEndian.Uint64(v), true, nil } +func (d *DefaultTxBlockIndex) BlockNumber(tx kv.Tx, txNum uint64) (blockNum uint64, ok bool, err error) { + c, err := tx.Cursor(kv.MaxTxNum) + if err != nil { + return 0, false, err + } + defer c.Close() + + _blk, v, err := c.Last() + if err != nil { + return 0, false, err + } + if _blk == nil { + return 0, false, nil + } + if len(_blk) != 8 { + return 0, false, fmt.Errorf("DefaultReadTxNumFunc: seems broken TxNum value: %x", _blk) + } + + lastBlockNum := binary.BigEndian.Uint64(_blk) + txNumInDb := binary.BigEndian.Uint64(v) + + if txNumInDb < txNum { + return 0, false, nil + } + + if lastBlockNum > uint64(math.MaxInt32) { + panic(fmt.Sprintf("block number is too big: %d", lastBlockNum)) + } + + { + // check genesis block + firstMaxTxNum, ok, err := d.MaxTxNum(tx, c, 0) + if err != nil { + return 0, false, fmt.Errorf("DefaultReadTxNumFunc first maxtxnum error: %w", err) + } + if ok { + if txNum <= firstMaxTxNum { + return 0, true, nil + } + } + } + + _blk, err = SecondKeyC(c) // first key is always genesis; can be found in blocks... + if err != nil { + return 0, false, err + } + if len(_blk) != 8 { + return 0, false, fmt.Errorf("DefaultReadTxNumFunc Second: seems broken TxNum value: %x", _blk) + } + secondBlockNum := binary.BigEndian.Uint64(_blk) + + blockNum = uint64(sort.Search(int(lastBlockNum+1), func(sblk int) bool { + if err != nil { + return true + } + if secondBlockNum > uint64(sblk) { + return false + } + var maxTxNum uint64 + maxTxNum, ok, err = d.MaxTxNum(tx, c, uint64(sblk)) + if err != nil { + return true + } + + if !ok { + _fb, _ft, _ := c.First() + _lb, _lt, _ := c.Last() + fb := binary.BigEndian.Uint64(_fb) + lt := binary.BigEndian.Uint64(_lt) + ft := binary.BigEndian.Uint64(_ft) + lb := binary.BigEndian.Uint64(_lb) + err = fmt.Errorf("BlockNum(%d): seems broken TxNum value: %d -> %d; db has: (%d-%d, %d-%d)", sblk, txNum, maxTxNum, fb, ft, lb, lt) + return true + } + return maxTxNum >= txNum + })) + if err != nil { + return 0, false, err + } + return blockNum, true, nil +} + // DefaultTxNums - default implementation of TxNums var TxNums TxNumsReader = TxNumsReader{ - readTxNumFunc: DefaultReadTxNumFunc, + index: DefaultTxBlockIndexInstance, } +var DefaultTxBlockIndexInstance = &DefaultTxBlockIndex{} -func (TxNumsReader) WithCustomReadTxNumFunc(f ReadTxNumFunc) TxNumsReader { - return TxNumsReader{readTxNumFunc: f} +func (TxNumsReader) WithCustomReadTxNumFunc(f TxBlockIndex) TxNumsReader { + return TxNumsReader{index: f} } // Min - returns maxTxNum in given block. If block not found - return last available value (`latest`/`pending` state) @@ -91,7 +188,7 @@ func (t TxNumsReader) Max(tx kv.Tx, blockNum uint64) (maxTxNum uint64, err error } defer c.Close() - maxTxNum, ok, err := t.readTxNumFunc(tx, c, blockNum) + maxTxNum, ok, err := t.index.MaxTxNum(tx, c, blockNum) if err != nil { return 0, err } @@ -120,7 +217,7 @@ func (t TxNumsReader) Min(tx kv.Tx, blockNum uint64) (maxTxNum uint64, err error } defer c.Close() - maxTxNum, ok, err := t.readTxNumFunc(tx, c, blockNum-1) + maxTxNum, ok, err := t.index.MaxTxNum(tx, c, blockNum-1) if err != nil { return 0, err } @@ -187,51 +284,10 @@ func (TxNumsReader) Truncate(tx kv.RwTx, blockNum uint64) (err error) { } return nil } -func (t TxNumsReader) FindBlockNum(tx kv.Tx, endTxNumMinimax uint64) (ok bool, blockNum uint64, err error) { - var seek [8]byte - c, err := tx.Cursor(kv.MaxTxNum) - if err != nil { - return false, 0, err - } - defer c.Close() - - lastK, _, err := c.Last() - if err != nil { - return false, 0, err - } - if lastK == nil { - return false, 0, nil - } - if len(lastK) != 8 { - return false, 0, fmt.Errorf("FindBlockNum: seems broken TxNum value: %x", lastK) - } - lastBlockNum := binary.BigEndian.Uint64(lastK) - - blockNum = uint64(sort.Search(int(lastBlockNum+1), func(i int) bool { - if err != nil { // don't loose errors from prev iterations - return true - } - var maxTxNum uint64 - maxTxNum, ok, err = t.readTxNumFunc(tx, c, uint64(i)) - if err != nil { - return true - } - if !ok { - _fb, _ft, _ := t.First(tx) - _lb, _lt, _ := t.Last(tx) - err = fmt.Errorf("FindBlockNum(%d): seems broken TxNum value: %x -> (%d, %d); db has: (%d-%d, %d-%d)", endTxNumMinimax, seek, i, maxTxNum, _fb, _lb, _ft, _lt) - return true - } - return maxTxNum >= endTxNumMinimax - })) - if err != nil { - return false, 0, err - } - if blockNum > lastBlockNum { - return false, 0, nil - } - return true, blockNum, nil +func (t TxNumsReader) FindBlockNum(tx kv.Tx, endTxNumMinimax uint64) (blockNum uint64, ok bool, err error) { + return t.index.BlockNumber(tx, endTxNumMinimax) } + func (TxNumsReader) Last(tx kv.Tx) (blockNum, txNum uint64, err error) { c, err := tx.Cursor(kv.MaxTxNum) if err != nil { @@ -300,7 +356,11 @@ func SecondKey(tx kv.Tx, table string) ([]byte, error) { return nil, err } defer c.Close() - _, _, err = c.First() + return SecondKeyC(c) +} + +func SecondKeyC(c kv.Cursor) ([]byte, error) { + _, _, err := c.First() if err != nil { return nil, err } @@ -352,7 +412,7 @@ func (i *MapTxNum2BlockNumIter) Next() (txNum, blockNum uint64, txIndex int, isF blockNumChanged = true var ok bool - ok, i.blockNum, err = i.txNumsReader.FindBlockNum(i.tx, txNum) + i.blockNum, ok, err = i.txNumsReader.FindBlockNum(i.tx, txNum) if err != nil { return } diff --git a/erigon-lib/kv/rawdbv3/txnum_test.go b/erigon-lib/kv/rawdbv3/txnum_test.go index ec9d835a26f..953aa328c96 100644 --- a/erigon-lib/kv/rawdbv3/txnum_test.go +++ b/erigon-lib/kv/rawdbv3/txnum_test.go @@ -39,30 +39,30 @@ func TestName(t *testing.T) { require.NoError(TxNums.Append(tx, 1, 99)) require.NoError(TxNums.Append(tx, 2, 100)) - _, n, err := TxNums.FindBlockNum(tx, 10) + n, _, err := TxNums.FindBlockNum(tx, 10) require.NoError(err) require.Equal(1, int(n)) - _, n, err = TxNums.FindBlockNum(tx, 0) + n, _, err = TxNums.FindBlockNum(tx, 0) require.NoError(err) require.Equal(0, int(n)) - _, n, err = TxNums.FindBlockNum(tx, 3) + n, _, err = TxNums.FindBlockNum(tx, 3) require.NoError(err) require.Equal(0, int(n)) - _, n, err = TxNums.FindBlockNum(tx, 4) + n, _, err = TxNums.FindBlockNum(tx, 4) require.NoError(err) require.Equal(1, int(n)) - _, n, err = TxNums.FindBlockNum(tx, 99) + n, _, err = TxNums.FindBlockNum(tx, 99) require.NoError(err) require.Equal(1, int(n)) - _, n, err = TxNums.FindBlockNum(tx, 100) + n, _, err = TxNums.FindBlockNum(tx, 100) require.NoError(err) require.Equal(2, int(n)) - ok, _, err := TxNums.FindBlockNum(tx, 101) + _, ok, err := TxNums.FindBlockNum(tx, 101) require.NoError(err) require.Equal(false, ok) return nil diff --git a/erigon-lib/kv/tables.go b/erigon-lib/kv/tables.go index 4e94b4c1075..73bd1705d98 100644 --- a/erigon-lib/kv/tables.go +++ b/erigon-lib/kv/tables.go @@ -168,6 +168,7 @@ const ( BorEvents = "BorEvents" // event_id -> event_payload BorEventNums = "BorEventNums" // block_num -> event_id (last event_id in that block) BorEventProcessedBlocks = "BorEventProcessedBlocks" // block_num -> block_time, tracks processed blocks in the bridge, used for unwinds and restarts, gets pruned + BorEventTimes = "BorEventTimes" // timestamp -> event_id BorSpans = "BorSpans" // span_id -> span (in JSON encoding) BorMilestones = "BorMilestones" // milestone_id -> milestone (in JSON encoding) BorMilestoneEnds = "BorMilestoneEnds" // start block_num -> milestone_id (first block of milestone) @@ -383,6 +384,7 @@ var ChaindataTables = []string{ BorEvents, BorEventNums, BorEventProcessedBlocks, + BorEventTimes, BorSpans, BorMilestones, BorMilestoneEnds, @@ -626,6 +628,7 @@ var BorTablesCfg = TableCfg{ BorEvents: {Flags: DupSort}, BorEventNums: {Flags: DupSort}, BorEventProcessedBlocks: {Flags: DupSort}, + BorEventTimes: {Flags: DupSort}, BorSpans: {Flags: DupSort}, BorCheckpoints: {Flags: DupSort}, BorCheckpointEnds: {Flags: DupSort}, diff --git a/erigon-lib/recsplit/recsplit.go b/erigon-lib/recsplit/recsplit.go index 5bbc69d395c..a6b990ea5be 100644 --- a/erigon-lib/recsplit/recsplit.go +++ b/erigon-lib/recsplit/recsplit.go @@ -224,9 +224,13 @@ func (rs *RecSplit) Salt() uint32 { return rs.salt } func (rs *RecSplit) Close() { if rs.indexF != nil { rs.indexF.Close() + _ = os.Remove(rs.indexF.Name()) + rs.indexF = nil } if rs.existenceF != nil { rs.existenceF.Close() + _ = os.Remove(rs.existenceF.Name()) + rs.existenceF = nil } if rs.bucketCollector != nil { rs.bucketCollector.Close() diff --git a/erigon-lib/state/aggregator.go b/erigon-lib/state/aggregator.go index 23832a1fc7a..2179ab6519a 100644 --- a/erigon-lib/state/aggregator.go +++ b/erigon-lib/state/aggregator.go @@ -25,7 +25,6 @@ import ( "os" "path/filepath" "runtime" - "slices" "sort" "strings" "sync" @@ -36,6 +35,7 @@ import ( "github.com/c2h5oh/datasize" "github.com/tidwall/btree" rand2 "golang.org/x/exp/rand" + "golang.org/x/exp/slices" "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" diff --git a/erigon-lib/state/aggregator2.go b/erigon-lib/state/aggregator2.go index 8109d6589c4..2bfd43e451b 100644 --- a/erigon-lib/state/aggregator2.go +++ b/erigon-lib/state/aggregator2.go @@ -2,6 +2,10 @@ package state import ( "context" + "errors" + "io/fs" + "path/filepath" + "strings" "github.com/erigontech/erigon-lib/common/datadir" "github.com/erigontech/erigon-lib/common/dbg" @@ -18,6 +22,10 @@ func NewAggregator2(ctx context.Context, dirs datadir.Dirs, aggregationStep uint return nil, err } + if err := Compatibility(dirs); err != nil { + panic(err) + } + a, err := NewAggregator(ctx, dirs, aggregationStep, db, logger) if err != nil { return nil, err @@ -279,3 +287,45 @@ func EnableHistoricalRCache() { } var ExperimentalConcurrentCommitment = false // set true to use concurrent commitment by default + +func isNewVerFormat(name string) bool { + if !strings.HasPrefix(name, "v") { + return false + } + name = strings.TrimPrefix(name, "v") + parts := strings.SplitN(name, "-", 2) + version := parts[0] + return strings.Count(version, ".") == 1 +} + +func Compatibility(d datadir.Dirs) error { + directories := []string{ + d.Chaindata, d.Tmp, d.SnapIdx, d.SnapHistory, d.SnapDomain, + d.SnapAccessors, d.SnapCaplin, d.Downloader, d.TxPool, d.Snap, + d.Nodes, d.CaplinBlobs, d.CaplinIndexing, d.CaplinLatest, d.CaplinGenesis, + } + for _, dirPath := range directories { + err := filepath.WalkDir(dirPath, func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !entry.IsDir() { + name := entry.Name() + if isNewVerFormat(name) { + return errors.New("the current snapshots are not compatible with this version of Erigon. " + + "Please ensure you're using Erigon v3.1 or later, or run " + + "`erigon seg reset-to-old-ver-format` to revert snapshots to the old format " + + "(they would be compatible with this 3.0)") + } + } + return nil + }) + + if err != nil { + return err + } + } + + return nil +} diff --git a/erigon-lib/state/domain.go b/erigon-lib/state/domain.go index 6ce8d1fd0d6..0c5df8b4168 100644 --- a/erigon-lib/state/domain.go +++ b/erigon-lib/state/domain.go @@ -133,7 +133,7 @@ func NewDomain(cfg domainCfg, logger log.Logger) (*Domain, error) { d := &Domain{ domainCfg: cfg, - dirtyFiles: btree2.NewBTreeGOptions[*filesItem](filesItemLess, btree2.Options{Degree: 128, NoLocks: false}), + dirtyFiles: btree2.NewBTreeGOptions(filesItemLess, btree2.Options{Degree: 128, NoLocks: false}), _visible: newDomainVisible(cfg.name, []visibleFile{}), } @@ -1283,7 +1283,7 @@ func (d *Domain) BuildMissedAccessors(ctx context.Context, g *errgroup.Group, ps } } -func buildHashMapAccessor(ctx context.Context, d *seg.Decompressor, compressed seg.FileCompression, idxPath string, values bool, cfg recsplit.RecSplitArgs, ps *background.ProgressSet, logger log.Logger) error { +func buildHashMapAccessor(ctx context.Context, d *seg.Decompressor, compressed seg.FileCompression, idxPath string, values bool, cfg recsplit.RecSplitArgs, ps *background.ProgressSet, logger log.Logger) (err error) { count := d.Count() if !values { count = d.Count() / 2 @@ -1293,7 +1293,6 @@ func buildHashMapAccessor(ctx context.Context, d *seg.Decompressor, compressed s g := seg.NewReader(d.MakeGetter(), compressed) var rs *recsplit.RecSplit - var err error cfg.KeyCount = count if rs, err = recsplit.NewRecSplit(cfg, logger); err != nil { return fmt.Errorf("create recsplit: %w", err) @@ -1303,6 +1302,12 @@ func buildHashMapAccessor(ctx context.Context, d *seg.Decompressor, compressed s p := ps.AddNew(rs.FileName(), uint64(count)) defer ps.Delete(p) + defer func() { + if rec := recover(); rec != nil { + err = fmt.Errorf("buildHashMapAccessor: %s, %s, %s", rs.FileName(), rec, dbg.Stack()) + } + }() + var keyPos, valPos uint64 for { word := make([]byte, 0, 256) diff --git a/erigon-lib/state/domain_shared.go b/erigon-lib/state/domain_shared.go index 84379e1d494..bd8e8890097 100644 --- a/erigon-lib/state/domain_shared.go +++ b/erigon-lib/state/domain_shared.go @@ -1360,3 +1360,11 @@ func (sdc *SharedDomainsCommitmentContext) restorePatriciaState(value []byte) (u func toStringZeroCopy(v []byte) string { return unsafe.String(&v[0], len(v)) } func toBytesZeroCopy(s string) []byte { return unsafe.Slice(unsafe.StringData(s), len(s)) } + +func AggTx(tx kv.Tx) *AggregatorRoTx { + if withAggTx, ok := tx.(interface{ AggTx() any }); ok { + return withAggTx.AggTx().(*AggregatorRoTx) + } + + return nil +} diff --git a/erigon-lib/state/history.go b/erigon-lib/state/history.go index a4d121048a2..a1eca79f67b 100644 --- a/erigon-lib/state/history.go +++ b/erigon-lib/state/history.go @@ -1130,6 +1130,9 @@ func (ht *HistoryRoTx) prune(ctx context.Context, rwTx kv.RwTx, txFrom, txTo, li if err != nil { return err } + if len(vv) < 8 { + return fmt.Errorf("prune history %s got invalid value length: %d < 8", ht.h.filenameBase, len(vv)) + } if vtx := binary.BigEndian.Uint64(vv); vtx != txNum { return fmt.Errorf("prune history %s got invalid txNum: found %d != %d wanted", ht.h.filenameBase, vtx, txNum) } diff --git a/erigon-lib/state/inverted_index.go b/erigon-lib/state/inverted_index.go index d9ab459cb4b..def0abd08e8 100644 --- a/erigon-lib/state/inverted_index.go +++ b/erigon-lib/state/inverted_index.go @@ -575,7 +575,7 @@ func (iit *InvertedIndexRoTx) seekInFiles(key []byte, txNum uint64) (found bool, } if txNum < iit.files[0].startTxNum { - return false, 0, fmt.Errorf("seek with txNum=%d but data before txNum=%d is not available", txNum, iit.files[0].startTxNum) + return false, 0, fmt.Errorf("seekInFiles(invIndex=%s,txNum=%d) but data before txNum=%d not available", iit.name.String(), txNum, iit.files[0].startTxNum) } if iit.files[len(iit.files)-1].endTxNum <= txNum { return false, 0, nil diff --git a/erigon-lib/state/merge.go b/erigon-lib/state/merge.go index 0ec9e6f8730..ee149c18f9f 100644 --- a/erigon-lib/state/merge.go +++ b/erigon-lib/state/merge.go @@ -939,12 +939,12 @@ func (dt *DomainRoTx) cleanAfterMerge(mergedDomain, mergedHist, mergedIdx *files // in this case we need keep small files, but when history already merged to `frozen` state - then we can cleanup // all earlier small files, by mark tem as `canDelete=true` func (ht *HistoryRoTx) cleanAfterMerge(merged, mergedIdx *filesItem) { + ht.iit.cleanAfterMerge(mergedIdx) if merged != nil && merged.endTxNum == 0 { return } outs := ht.garbage(merged) deleteMergeFile(ht.h.dirtyFiles, outs, ht.h.filenameBase, ht.h.logger) - ht.iit.cleanAfterMerge(mergedIdx) } // cleanAfterMerge - mark all small files before `f` as `canDelete=true` diff --git a/erigon-lib/state/squeeze.go b/erigon-lib/state/squeeze.go index 187c2ac98e0..97ad1245579 100644 --- a/erigon-lib/state/squeeze.go +++ b/erigon-lib/state/squeeze.go @@ -373,7 +373,7 @@ func (a *Aggregator) RebuildCommitmentFiles(ctx context.Context, rwDb kv.RwDB, t } defer roTx.Rollback() - _, blockNum, err := txNumsReader.FindBlockNum(roTx, toTxNumRange-1) + blockNum, _, err := txNumsReader.FindBlockNum(roTx, toTxNumRange-1) if err != nil { a.logger.Warn("failed to find block number for txNum", "txNum", toTxNumRange, "err", err) return nil, err diff --git a/eth/backend.go b/eth/backend.go index 67ad4be22fd..26272585881 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -66,6 +66,7 @@ import ( "github.com/erigontech/erigon-lib/downloader/downloadercfg" "github.com/erigontech/erigon-lib/downloader/downloadergrpc" "github.com/erigontech/erigon-lib/downloader/snaptype" + "github.com/erigontech/erigon-lib/event" protodownloader "github.com/erigontech/erigon-lib/gointerfaces/downloaderproto" "github.com/erigontech/erigon-lib/gointerfaces/grpcutil" remote "github.com/erigontech/erigon-lib/gointerfaces/remoteproto" @@ -134,7 +135,6 @@ import ( "github.com/erigontech/erigon/turbo/silkworm" "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" stages2 "github.com/erigontech/erigon/turbo/stages" - "github.com/erigontech/erigon/turbo/stages/headerdownload" "github.com/erigontech/erigon/txnprovider" "github.com/erigontech/erigon/txnprovider/shutter" "github.com/erigontech/erigon/txnprovider/txpool" @@ -177,9 +177,10 @@ type Ethereum struct { rpcFilters *rpchelper.Filters rpcDaemonStateCache kvcache.Cache - miningSealingQuit chan struct{} - pendingBlocks chan *types.Block - minedBlocks chan *types.Block + miningSealingQuit chan struct{} + pendingBlocks chan *types.Block + minedBlocks chan *types.Block + minedBlockObservers *event.Observers[*types.Block] sentryCtx context.Context sentryCancel context.CancelFunc @@ -343,6 +344,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger blockBuilderNotifyNewTxns: make(chan struct{}, 1), miningSealingQuit: make(chan struct{}), minedBlocks: make(chan *types.Block, 1), + minedBlockObservers: event.NewObservers[*types.Block](), logger: logger, stopNode: func() error { return stack.Close() @@ -384,6 +386,8 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger backend.genesisBlock = genesis backend.genesisHash = genesis.Hash() + setDefaultMinerGasLimit(chainConfig, config, logger) + setBorDefaultMinerGasPrice(chainConfig, config, logger) setBorDefaultTxPoolPriceLimit(chainConfig, config.TxPool, logger) @@ -597,7 +601,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger if chainConfig.Bor != nil { if !config.WithoutHeimdall { - heimdallClient = heimdall.NewHttpClient(config.HeimdallURL, logger) + heimdallClient = heimdall.NewHttpClient(config.HeimdallURL, logger, heimdall.WithApiVersioner(ctx)) } if config.PolygonSync { @@ -817,26 +821,13 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger blobStore = parlia.BlobStore } + astridEnabled := chainConfig.Bor != nil && config.PolygonSync + // proof-of-work mining mining := stagedsync.New( config.Sync, stagedsync.MiningStages(backend.sentryCtx, stagedsync.StageMiningCreateBlockCfg(backend.chainDB, miner, *backend.chainConfig, backend.engine, nil, tmpdir, backend.blockReader), - stagedsync.StageBorHeimdallCfg( - backend.chainDB, - snapDb, - miner, - *backend.chainConfig, - heimdallClient, - heimdallStore, - bridgeStore, - backend.blockReader, - nil, - nil, - recents, - signatures, - false, - nil), stagedsync.StageExecuteBlocksCfg( backend.chainDB, config.Prune, @@ -858,6 +849,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger stagedsync.StageSendersCfg(backend.chainDB, chainConfig, config.Sync, false, dirs.Tmp, config.Prune, blockReader, backend.sentriesClient.Hd), stagedsync.StageMiningExecCfg(backend.chainDB, miner, backend.notifications.Events, *backend.chainConfig, backend.engine, &vm.Config{}, tmpdir, nil, 0, txnProvider, blockReader), stagedsync.StageMiningFinishCfg(backend.chainDB, *backend.chainConfig, backend.engine, miner, backend.miningSealingQuit, backend.blockReader, latestBlockBuiltStore), + astridEnabled, ), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder, logger, stages.ModeBlockProduction) @@ -869,21 +861,6 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger config.Sync, stagedsync.MiningStages(backend.sentryCtx, stagedsync.StageMiningCreateBlockCfg(backend.chainDB, miningStatePos, *backend.chainConfig, backend.engine, param, tmpdir, backend.blockReader), - stagedsync.StageBorHeimdallCfg( - backend.chainDB, - snapDb, - miningStatePos, - *backend.chainConfig, - heimdallClient, - heimdallStore, - bridgeStore, - backend.blockReader, - nil, - nil, - recents, - signatures, - false, - nil), stagedsync.StageExecuteBlocksCfg( backend.chainDB, config.Prune, @@ -904,7 +881,9 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger ), stagedsync.StageSendersCfg(backend.chainDB, chainConfig, config.Sync, false, dirs.Tmp, config.Prune, blockReader, backend.sentriesClient.Hd), stagedsync.StageMiningExecCfg(backend.chainDB, miningStatePos, backend.notifications.Events, *backend.chainConfig, backend.engine, &vm.Config{}, tmpdir, interrupt, param.PayloadId, txnProvider, blockReader), - stagedsync.StageMiningFinishCfg(backend.chainDB, *backend.chainConfig, backend.engine, miningStatePos, backend.miningSealingQuit, backend.blockReader, latestBlockBuiltStore)), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder, logger, stages.ModeBlockProduction) + stagedsync.StageMiningFinishCfg(backend.chainDB, *backend.chainConfig, backend.engine, miningStatePos, backend.miningSealingQuit, backend.blockReader, latestBlockBuiltStore), + astridEnabled, + ), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder, logger, stages.ModeBlockProduction) // We start the mining step if err := stages2.MiningStep(ctx, backend.chainDB, proposingSync, tmpdir, logger); err != nil { return nil, err @@ -948,14 +927,18 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger for { select { case b := <-backend.minedBlocks: - // Add mined header and block body before broadcast. This is because the broadcast call - // will trigger the staged sync which will require headers and blocks to be available - // in their respective cache in the download stage. If not found, it would cause a - // liveness issue for the chain. - if err := backend.sentriesClient.Hd.AddMinedHeader(b.Header()); err != nil { - logger.Error("add mined block to header downloader", "err", err) + if !sentryMcDisableBlockDownload { + // Add mined header and block body before broadcast. This is because the broadcast call + // will trigger the staged sync which will require headers and blocks to be available + // in their respective cache in the download stage. If not found, it would cause a + // liveness issue for the chain. + if err := backend.sentriesClient.Hd.AddMinedHeader(b.Header()); err != nil { + logger.Error("add mined block to header downloader", "err", err) + } + backend.sentriesClient.Bd.AddToPrefetch(b.Header(), b.RawBody()) } - backend.sentriesClient.Bd.AddToPrefetch(b.Header(), b.RawBody()) + + backend.minedBlockObservers.Notify(b) //p2p //backend.sentriesClient.BroadcastNewBlock(context.Background(), b, b.Difficulty()) @@ -963,13 +946,6 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger if err := backend.miningRPC.BroadcastMinedBlock(b); err != nil { logger.Error("txpool rpc mined block broadcast", "err", err) } - logger.Trace("BroadcastMinedBlock successful", "number", b.Number(), "GasUsed", b.GasUsed(), "txn count", b.Transactions().Len()) - backend.sentriesClient.PropagateNewBlockHashes(ctx, []headerdownload.Announce{ - { - Number: b.NumberU64(), - Hash: b.Hash(), - }, - }) case b := <-backend.pendingBlocks: if err := backend.miningRPC.BroadcastPendingBlock(b); err != nil { @@ -1017,6 +993,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger statusDataProvider, backend.stopNode, &engineAPISwitcher{backend: backend}, + backend, ) backend.syncUnwindOrder = stagedsync.PolygonSyncUnwindOrder backend.syncPruneOrder = stagedsync.PolygonSyncPruneOrder @@ -1121,6 +1098,7 @@ func New(ctx context.Context, stack *node.Node, config *ethconfig.Config, logger heimdallService, backend.notifications, backend.engineBackendRPC, + backend, ) // we need to initiate download before the heimdall services start rather than @@ -1304,20 +1282,6 @@ func (s *Ethereum) StartMining(ctx context.Context, db kv.RwDB, stateDiffClient borcfg.Authorize(eb, func(_ libcommon.Address, mimeType string, message []byte) ([]byte, error) { return crypto.Sign(crypto.Keccak256(message), miner.MiningConfig.SigKey) }) - - if !s.config.WithoutHeimdall { - err := stagedsync.FetchSpanZeroForMiningIfNeeded( - ctx, - s.chainDB, - s.blockReader, - borcfg.HeimdallClient, - heimdallStore, - logger, - ) - if err != nil { - return err - } - } } else if s.chainConfig.Consensus == chain.CliqueConsensus { s.engine.(*clique.Clique).Authorize(eb, func(_ libcommon.Address, _ string, msg []byte) ([]byte, error) { return crypto.Sign(crypto.Keccak256(msg), miner.MiningConfig.SigKey) @@ -1482,6 +1446,10 @@ func (s *Ethereum) StartMining(ctx context.Context, db kv.RwDB, stateDiffClient func (s *Ethereum) IsMining() bool { return s.config.Miner.Enabled } +func (s *Ethereum) RegisterMinedBlockObserver(callback func(msg *types.Block)) event.UnregisterFunc { + return s.minedBlockObservers.Register(callback) +} + func (s *Ethereum) ChainKV() kv.RwDB { return s.chainDB } func (s *Ethereum) NetVersion() (uint64, error) { return s.networkID, nil } func (s *Ethereum) NetPeerCount() (uint64, error) { @@ -1951,6 +1919,20 @@ func setBorDefaultMinerGasPrice(chainConfig *chain.Config, config *ethconfig.Con } } +func setDefaultMinerGasLimit(chainConfig *chain.Config, config *ethconfig.Config, logger log.Logger) { + if chainConfig.Bor != nil { + if config.Miner.GasLimit == nil { + gasLimit := ethconfig.BorDefaultMinerGasLimit + config.Miner.GasLimit = &gasLimit + } + } else { + if config.Miner.GasLimit == nil { + gasLimit := ethconfig.DefaultMinerGasLimit + config.Miner.GasLimit = &gasLimit + } + } +} + // setBorDefaultTxPoolPriceLimit enforces MinFeeCap to be equal to BorDefaultTxPoolPriceLimit (25gwei by default) func setBorDefaultTxPoolPriceLimit(chainConfig *chain.Config, config txpoolcfg.Config, logger log.Logger) { if chainConfig.Bor != nil && config.MinFeeCap != txpoolcfg.BorDefaultTxPoolPriceLimit { diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 3e71a187e56..8983fa43ee7 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -48,7 +48,10 @@ import ( ) // BorDefaultMinerGasPrice defines the minimum gas price for bor validators to mine a transaction. -var BorDefaultMinerGasPrice = big.NewInt(25 * params.GWei) +var BorDefaultMinerGasPrice = big.NewInt(25 * common.GWei) +var BorDefaultMinerGasLimit uint64 = 45_000_000 + +var DefaultMinerGasLimit uint64 = 36_000_000 // FullNodeGPO contains default gasprice oracle settings for full node. var FullNodeGPO = gaspricecfg.Config{ @@ -91,8 +94,7 @@ var Defaults = Config{ NetworkID: 1, Prune: prune.DefaultMode, Miner: params.MiningConfig{ - GasLimit: 36_000_000, - GasPrice: big.NewInt(params.GWei), + GasPrice: big.NewInt(common.GWei), Recommit: 3 * time.Second, }, TxPool: txpoolcfg.DefaultConfig, diff --git a/eth/integrity/bor_snapshots.go b/eth/integrity/bor_snapshots.go index 925ddfd1b74..4dd2f39af5e 100644 --- a/eth/integrity/bor_snapshots.go +++ b/eth/integrity/bor_snapshots.go @@ -95,31 +95,17 @@ func ValidateBorEvents(ctx context.Context, db kv.TemporalRoDB, blockReader serv return err } - borHeimdallProgress, err := stages.GetStageProgress(tx, stages.BorHeimdall) - if err != nil { - return err - } - polygonSyncProgress, err := stages.GetStageProgress(tx, stages.PolygonSync) if err != nil { return err } - // bor heimdall and polygon sync are mutually exclusive, bor heimdall will be removed soon - polygonSyncProgress = max(borHeimdallProgress, polygonSyncProgress) - bodyProgress, err := stages.GetStageProgress(tx, stages.Bodies) if err != nil { return err } log.Info("[integrity] LAST Event", "event", lastEventId, "bor-progress", polygonSyncProgress, "body-progress", bodyProgress) - - if bodyProgress > borHeimdallProgress { - for blockNum := maxBlockNum + 1; blockNum <= bodyProgress; blockNum++ { - - } - } } return nil @@ -143,6 +129,7 @@ func ValidateBorSpans(ctx context.Context, logger log.Logger, dirs datadir.Dirs, return err } defer snapshotStore.Close() + defer baseStore.Close() err = snapshotStore.ValidateSnapshots(ctx, logger, failFast) logger.Info("[integrity] ValidateBorSpans: done", "err", err) return err @@ -156,6 +143,7 @@ func ValidateBorCheckpoints(ctx context.Context, logger log.Logger, dirs datadir return err } defer snapshotStore.Close() + defer baseStore.Close() err = snapshotStore.ValidateSnapshots(ctx, logger, failFast) logger.Info("[integrity] ValidateBorCheckpoints: done", "err", err) return err @@ -169,6 +157,7 @@ func ValidateBorMilestones(ctx context.Context, logger log.Logger, dirs datadir. return err } defer snapshotStore.Close() + defer baseStore.Close() err = snapshotStore.ValidateSnapshots(ctx, logger, failFast) logger.Info("[integrity] ValidateBorMilestones: done", "err", err) return err diff --git a/eth/integrity/e3_history_no_system_txs.go b/eth/integrity/e3_history_no_system_txs.go index f5340ea7d7b..966665def01 100644 --- a/eth/integrity/e3_history_no_system_txs.go +++ b/eth/integrity/e3_history_no_system_txs.go @@ -23,6 +23,7 @@ import ( "sync/atomic" "time" + "github.com/erigontech/erigon/eth/ethconfig/estimate" "golang.org/x/sync/errgroup" "github.com/erigontech/erigon-lib/kv" @@ -42,6 +43,7 @@ func HistoryCheckNoSystemTxs(ctx context.Context, db kv.TemporalRwDB, blockReade defer logEvery.Stop() agg := db.(state.HasAgg).Agg().(*state.Aggregator) g := &errgroup.Group{} + g.SetLimit(estimate.AlmostAllCPUs()) for j := 0; j < 256; j++ { j := j for jj := 0; jj < 255; jj++ { @@ -54,13 +56,13 @@ func HistoryCheckNoSystemTxs(ctx context.Context, db kv.TemporalRwDB, blockReade defer tx.Rollback() var minStep uint64 = math.MaxUint64 - keys, err := tx.(state.HasAggTx).AggTx().(*state.AggregatorRoTx).DebugRangeLatest(tx, kv.AccountsDomain, []byte{byte(j), byte(jj)}, []byte{byte(j), byte(jj + 1)}, -1) + keys, err := state.AggTx(tx).DebugRangeLatest(tx, kv.AccountsDomain, []byte{byte(j), byte(jj)}, []byte{byte(j), byte(jj + 1)}, -1) if err != nil { return err } defer keys.Close() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)) for keys.HasNext() { key, _, err := keys.Next() @@ -76,7 +78,7 @@ func HistoryCheckNoSystemTxs(ctx context.Context, db kv.TemporalRwDB, blockReade if err != nil { return err } - ok, blockNum, err := txNumsReader.FindBlockNum(tx, txNum) + blockNum, ok, err := txNumsReader.FindBlockNum(tx, txNum) if err != nil { return err } @@ -92,15 +94,15 @@ func HistoryCheckNoSystemTxs(ctx context.Context, db kv.TemporalRwDB, blockReade log.Info(fmt.Sprintf("[integrity] HistoryNoSystemTxs: minStep=%d, step=%d, txNum=%d, blockNum=%d, key=%x", minStep, txNum/agg.StepSize(), txNum, blockNum, key)) break } - - select { - case <-logEvery.C: - log.Info(fmt.Sprintf("[integrity] HistoryNoSystemTxs: checked=%dK keys", count.Load()/1_000)) - default: - } } it.Close() count.Add(1) + + select { + case <-logEvery.C: + log.Info(fmt.Sprintf("[integrity] HistoryNoSystemTxs: checked=%.2fm keys", float64(count.Load())/1_000_000)) + default: + } } return nil }) diff --git a/eth/integrity/integrity_action_type.go b/eth/integrity/integrity_action_type.go index c81dff3c07e..cec00adf96c 100644 --- a/eth/integrity/integrity_action_type.go +++ b/eth/integrity/integrity_action_type.go @@ -20,6 +20,7 @@ type Check string const ( Blocks Check = "Blocks" + HeaderNoGaps Check = "HeaderNoGaps" BlocksTxnID Check = "BlocksTxnID" InvertedIndex Check = "InvertedIndex" HistoryNoSystemTxs Check = "HistoryNoSystemTxs" diff --git a/eth/integrity/no_gaps_in_canonical_headers.go b/eth/integrity/no_gaps_in_canonical_headers.go index cbeea4c863f..04eea694d28 100644 --- a/eth/integrity/no_gaps_in_canonical_headers.go +++ b/eth/integrity/no_gaps_in_canonical_headers.go @@ -30,7 +30,13 @@ import ( "github.com/erigontech/erigon/turbo/services" ) -func NoGapsInCanonicalHeaders(tx kv.Tx, ctx context.Context, br services.FullBlockReader) { +func NoGapsInCanonicalHeaders(ctx context.Context, db kv.RoDB, br services.FullBlockReader, failFast bool) error { + tx, err := db.BeginRo(ctx) + if err != nil { + return err + } + defer tx.Rollback() + logEvery := time.NewTicker(10 * time.Second) defer logEvery.Stop() @@ -51,7 +57,10 @@ func NoGapsInCanonicalHeaders(tx kv.Tx, ctx context.Context, br services.FullBlo } if !ok || hash == (common.Hash{}) { err = fmt.Errorf("canonical marker not found: %d", i) - panic(err) + if failFast { + return err + } + log.Error(err.Error()) } header := rawdb.ReadHeader(tx, hash, i) if header == nil { @@ -61,15 +70,19 @@ func NoGapsInCanonicalHeaders(tx kv.Tx, ctx context.Context, br services.FullBlo body, _, _ := rawdb.ReadBody(tx, hash, i) if body == nil { err = fmt.Errorf("header not found: %d", i) - panic(err) + if failFast { + return err + } + log.Error(err.Error()) } select { case <-ctx.Done(): - return + return ctx.Err() case <-logEvery.C: log.Info("[integrity] NoGapsInCanonicalHeaders", "progress", fmt.Sprintf("%s/%s", common.PrettyCounter(i), common.PrettyCounter(lastBlockNum))) default: } } + return nil } diff --git a/eth/integrity/receipts_no_duplicates.go b/eth/integrity/receipts_no_duplicates.go index 0970d1a682f..7e005594a88 100644 --- a/eth/integrity/receipts_no_duplicates.go +++ b/eth/integrity/receipts_no_duplicates.go @@ -10,7 +10,6 @@ import ( "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon-lib/state" "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" - "github.com/erigontech/erigon/eth/stagedsync/stages" "github.com/erigontech/erigon/turbo/services" "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" ) @@ -29,65 +28,104 @@ func ReceiptsNoDuplicates(ctx context.Context, db kv.TemporalRoDB, blockReader s } defer tx.Rollback() + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)) + + receiptDomainProgress := state.AggTx(tx).HistoryProgress(kv.ReceiptDomain, tx) + fromBlock := uint64(1) - stageExecProgress, err := stages.GetStageProgress(tx, stages.Execution) - if err != nil { + toBlock, _, _ := txNumsReader.FindBlockNum(tx, receiptDomainProgress) + //stageExecProgress, err := stages.GetStageProgress(tx, stages.Execution) + //if err != nil { + // return err + //} + //toBlock := stageExecProgress + + { + ac := state.AggTx(tx) + log.Info("[integrity] ReceiptsNoDuplicates starting", "fromBlock", fromBlock, "toBlock", toBlock) + receiptProgress := ac.HistoryProgress(kv.ReceiptDomain, tx) + accProgress := ac.HistoryProgress(kv.AccountsDomain, tx) + if accProgress != receiptProgress { + err := fmt.Errorf("[integrity] ReceiptDomain=%d is behind AccountDomain=%d", receiptProgress, accProgress) + log.Warn(err.Error()) + } + } + + if err := ReceiptsNoDuplicatesRange(ctx, fromBlock, toBlock, tx, blockReader, failFast); err != nil { return err } - toBlock := stageExecProgress + return nil +} - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, blockReader)) +func ReceiptsNoDuplicatesRange(ctx context.Context, fromBlock, toBlock uint64, tx kv.TemporalTx, blockReader services.FullBlockReader, failFast bool) (err error) { + logEvery := time.NewTicker(10 * time.Second) + defer logEvery.Stop() + + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)) fromTxNum, err := txNumsReader.Min(tx, fromBlock) if err != nil { return err } + if fromTxNum < 2 { + fromTxNum = 2 //i don't remember why need this + } + if toBlock > 0 { toBlock-- // [fromBlock,toBlock) } - - ac := tx.(state.HasAggTx).AggTx().(*state.AggregatorRoTx) toTxNum, err := txNumsReader.Max(tx, toBlock) if err != nil { return err } - prevCumGasUsed := -1 - prevBN := uint64(1) - log.Info("[integrity] ReceiptsNoDuplicates starting", "fromTxNum", fromTxNum, "toTxNum", toTxNum) - { - receiptProgress := ac.HistoryProgress(kv.ReceiptDomain, tx) - accProgress := ac.HistoryProgress(kv.AccountsDomain, tx) - if accProgress != receiptProgress { - err := fmt.Errorf("[integrity] ReceiptDomain=%d is behind AccountDomain=%d", receiptProgress, accProgress) - log.Warn(err.Error()) - } - } - - var cumGasUsed uint64 + prevCumUsedGas := -1 + prevLogIdx := uint32(0) + prevBN := uint64(1) for txNum := fromTxNum; txNum <= toTxNum; txNum++ { - cumGasUsed, _, _, err = rawtemporaldb.ReceiptAsOf(tx, txNum) + cumUsedGas, _, logIdx, err := rawtemporaldb.ReceiptAsOf(tx, txNum) if err != nil { return err } - //_, blockNum, _ := txNumsReader.FindBlockNum(tx, txNum) blockNum := badFoundBlockNum(tx, prevBN-1, txNumsReader, txNum) - //fmt.Printf("[dbg] cumGasUsed=%d, txNum=%d, blockNum=%d, prevCumGasUsed=%d\n", cumGasUsed, txNum, blockNum, prevCumGasUsed) - if int(cumGasUsed) == prevCumGasUsed && cumGasUsed != 0 && blockNum == prevBN { - err := fmt.Errorf("bad receipt at txnum: %d, block: %d, cumGasUsed=%d, prevCumGasUsed=%d", txNum, blockNum, cumGasUsed, prevCumGasUsed) - panic(err) + _min, _ := txNumsReader.Min(tx, blockNum) + blockChanged := txNum == _min + if blockChanged { + prevCumUsedGas = 0 + prevLogIdx = 0 + } + + _max, _ := txNumsReader.Max(tx, blockNum) + + strongMonotonicCumGasUsed := int(cumUsedGas) > prevCumUsedGas + if !strongMonotonicCumGasUsed && cumUsedGas != 0 { + err := fmt.Errorf("ReceiptsNoDuplicates: non-monotonic cumGasUsed at txnum: %d, block: %d(%d-%d), cumGasUsed=%d, prevCumGasUsed=%d", txNum, blockNum, _min, _max, cumUsedGas, prevCumUsedGas) + if failFast { + return err + } + log.Error(err.Error()) } - prevCumGasUsed = int(cumGasUsed) + + monotonicLogIdx := logIdx >= prevLogIdx + if !monotonicLogIdx { + err := fmt.Errorf("ReceiptsNoDuplicates: non-monotonic logIndex at txnum: %d, block: %d(%d-%d), logIdx=%d, prevLogIdx=%d", txNum, blockNum, _min, _max, logIdx, prevLogIdx) + if failFast { + return err + } + log.Error(err.Error()) + } + + prevCumUsedGas = int(cumUsedGas) + prevLogIdx = logIdx prevBN = blockNum select { case <-ctx.Done(): - return + return ctx.Err() case <-logEvery.C: - log.Info("[integrity] ReceiptsNoDuplicates", "progress", fmt.Sprintf("%dk/%dk", blockNum/1_000, toBlock/1_000)) + log.Info("[integrity] ReceiptsNoDuplicates", "progress", fmt.Sprintf("%.1fm/%.1fm", float64(txNum)/1_000_000, float64(toTxNum)/1_000_000)) default: } } - return nil } diff --git a/eth/stagedsync/bor_heimdall_shared.go b/eth/stagedsync/bor_heimdall_shared.go deleted file mode 100644 index 860e8674bd4..00000000000 --- a/eth/stagedsync/bor_heimdall_shared.go +++ /dev/null @@ -1,500 +0,0 @@ -// Copyright 2024 The Erigon Authors -// This file is part of Erigon. -// -// Erigon 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. -// -// Erigon 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 Erigon. If not, see . - -package stagedsync - -import ( - "context" - "errors" - "fmt" - "strconv" - "time" - - "github.com/erigontech/erigon-lib/log/v3" - - "github.com/erigontech/erigon-lib/kv" - "github.com/erigontech/erigon/core/types" - "github.com/erigontech/erigon/polygon/bor/borcfg" - "github.com/erigontech/erigon/polygon/bridge" - "github.com/erigontech/erigon/polygon/heimdall" - "github.com/erigontech/erigon/turbo/services" -) - -var ( - ErrHeaderValidatorsLengthMismatch = errors.New("header validators length mismatch") - ErrHeaderValidatorsBytesMismatch = errors.New("header validators bytes mismatch") -) - -func FetchSpanZeroForMiningIfNeeded( - ctx context.Context, - db kv.RwDB, - blockReader services.FullBlockReader, - heimdallClient heimdall.Client, - heimdallStore heimdall.Store, - logger log.Logger, -) error { - return db.Update(ctx, func(tx kv.RwTx) error { - _, _, err := blockReader.Span(ctx, tx, 0) - if err != nil { - if errors.Is(err, heimdall.ErrSpanNotFound) { - _, err = fetchAndWriteHeimdallSpan(ctx, 0, tx, heimdallClient, heimdallStore, "FetchSpanZeroForMiningIfNeeded", logger) - return err - } - - return err - } - - return nil - }) -} - -func fetchRequiredHeimdallSpansIfNeeded( - ctx context.Context, - toBlockNum uint64, - tx kv.RwTx, - cfg BorHeimdallCfg, - logPrefix string, - logger log.Logger, -) (uint64, error) { - requiredSpanID := heimdall.SpanIdAt(toBlockNum) - if requiredSpanID == 0 && toBlockNum >= cfg.borConfig.CalculateSprintLength(toBlockNum) { - // when in span 0 we fetch the next span (span 1) at the beginning of sprint 2 (block 16 or later) - requiredSpanID++ - } else if heimdall.IsBlockInLastSprintOfSpan(toBlockNum, cfg.borConfig) { - // for subsequent spans, we always fetch the next span at the beginning of the last sprint of a span - requiredSpanID++ - } - - lastSpanID, exists, err := cfg.blockReader.LastSpanId(ctx, tx) - if err != nil { - return 0, err - } - - if exists && requiredSpanID <= heimdall.SpanId(lastSpanID) { - return lastSpanID, nil - } - - var from heimdall.SpanId - if lastSpanID > 0 { - from = heimdall.SpanId(lastSpanID + 1) - } // else fetch from span 0 - - logger.Info(fmt.Sprintf("[%s] Processing spans...", logPrefix), "from", from, "to", requiredSpanID) - for spanID := from; spanID <= requiredSpanID; spanID++ { - if _, err = fetchAndWriteHeimdallSpan(ctx, uint64(spanID), tx, cfg.heimdallClient, cfg.heimdallStore, logPrefix, logger); err != nil { - return 0, err - } - } - - return uint64(requiredSpanID), err -} - -func fetchAndWriteHeimdallSpan( - ctx context.Context, - spanID uint64, - tx kv.RwTx, - heimdallClient heimdall.Client, - heimdallStore heimdall.Store, - logPrefix string, - logger log.Logger, -) (uint64, error) { - response, err := heimdallClient.FetchSpan(ctx, spanID) - if err != nil { - return 0, err - } - - if err = heimdallStore.Spans().(interface { - WithTx(kv.Tx) heimdall.EntityStore[*heimdall.Span] - }).WithTx(tx).PutEntity(ctx, spanID, response); err != nil { - return 0, err - } - - if spanID%100 == 0 { - logger.Debug(fmt.Sprintf("[%s] Wrote span", logPrefix), "id", spanID) - } - return spanID, nil -} - -func fetchAndWriteHeimdallCheckpointsIfNeeded( - ctx context.Context, - toBlockNum uint64, - tx kv.RwTx, - cfg BorHeimdallCfg, - logPrefix string, - logger log.Logger, -) (uint64, error) { - - lastId, exists, err := cfg.blockReader.LastCheckpointId(ctx, tx) - - if err != nil { - return 0, err - } - - var lastCheckpoint *heimdall.Checkpoint - - if exists { - checkpoint, _, err := cfg.blockReader.Checkpoint(ctx, tx, lastId) - - if err != nil { - return 0, err - } - - lastCheckpoint = checkpoint - } - - logTimer := time.NewTicker(logInterval) - defer logTimer.Stop() - - count, err := cfg.heimdallClient.FetchCheckpointCount(ctx) - - if err != nil { - return 0, err - } - - logger.Info(fmt.Sprintf("[%s] Processing checkpoints...", logPrefix), "from", lastId+1, "to", toBlockNum, "count", count) - - var lastBlockNum uint64 - - for checkpointId := lastId + 1; checkpointId <= uint64(count) && (lastCheckpoint == nil || lastCheckpoint.EndBlock().Uint64() < toBlockNum); checkpointId++ { - if _, lastCheckpoint, err = fetchAndWriteHeimdallCheckpoint(ctx, checkpointId, tx, cfg.heimdallClient, cfg.heimdallStore, logPrefix, logger); err != nil { - if !errors.Is(err, heimdall.ErrNotInCheckpointList) { - return 0, err - } - - return lastId, err - } - - lastId = checkpointId - - select { - default: - case <-logTimer.C: - if lastCheckpoint != nil { - lastBlockNum = lastCheckpoint.EndBlock().Uint64() - } - - logger.Info( - fmt.Sprintf("[%s] Checkpoint Progress", logPrefix), - "progress", lastBlockNum, - "lastCheckpointId", lastId, - ) - } - } - - return lastId, err -} - -func fetchAndWriteHeimdallCheckpoint( - ctx context.Context, - checkpointId uint64, - tx kv.RwTx, - heimdallClient heimdall.Client, - heimdallStore heimdall.Store, - logPrefix string, - logger log.Logger, -) (uint64, *heimdall.Checkpoint, error) { - response, err := heimdallClient.FetchCheckpoint(ctx, int64(checkpointId)) - if err != nil { - return 0, nil, err - } - - if err = heimdallStore.Checkpoints().(interface { - WithTx(kv.Tx) heimdall.EntityStore[*heimdall.Checkpoint] - }).WithTx(tx).PutEntity(ctx, checkpointId, response); err != nil { - return 0, nil, err - } - - logger.Trace(fmt.Sprintf("[%s] Wrote checkpoint", logPrefix), "id", checkpointId, "start", response.StartBlock(), "end", response.EndBlock()) - return checkpointId, response, nil -} - -func fetchAndWriteHeimdallMilestonesIfNeeded( - ctx context.Context, - toBlockNum uint64, - tx kv.RwTx, - cfg BorHeimdallCfg, - logPrefix string, - logger log.Logger, -) (uint64, error) { - - lastId, exists, err := cfg.blockReader.LastMilestoneId(ctx, tx) - - if err != nil { - return 0, err - } - - var lastMilestone *heimdall.Milestone - - if exists { - milestone, _, err := cfg.blockReader.Milestone(ctx, tx, lastId) - - if err != nil { - return 0, err - } - - lastMilestone = milestone - } - - logger.Info(fmt.Sprintf("[%s] Processing milestones...", logPrefix), "from", lastId+1, "to", toBlockNum) - - count, err := cfg.heimdallClient.FetchMilestoneCount(ctx) - - if err != nil { - return 0, err - } - - // it seems heimdall does not keep may live milestones - if - // you try to get one before this you get an error on the api - - lastActive := uint64(count) - activeMilestones - - if lastId < lastActive { - for lastActive <= uint64(count) { - lastMilestone, err = cfg.heimdallClient.FetchMilestone(ctx, int64(lastActive)) - - if err != nil { - if !errors.Is(err, heimdall.ErrNotInMilestoneList) { - return lastId, err - } - - lastActive++ - continue - } - - break - } - - if lastMilestone == nil || toBlockNum < lastMilestone.StartBlock().Uint64() { - return lastId, nil - } - - lastId = lastActive - 1 - } - - for milestoneId := lastId + 1; milestoneId <= uint64(count) && (lastMilestone == nil || lastMilestone.EndBlock().Uint64() < toBlockNum); milestoneId++ { - if _, lastMilestone, err = fetchAndWriteHeimdallMilestone(ctx, milestoneId, tx, cfg.heimdallClient, cfg.heimdallStore, logPrefix, logger); err != nil { - if !errors.Is(err, heimdall.ErrNotInMilestoneList) { - return 0, err - } - - return lastId, nil - } - - lastId = milestoneId - } - - return lastId, err -} - -var activeMilestones uint64 = 100 - -func fetchAndWriteHeimdallMilestone( - ctx context.Context, - milestoneId uint64, - tx kv.RwTx, - heimdallClient heimdall.Client, - heimdallStore heimdall.Store, - logPrefix string, - logger log.Logger, -) (uint64, *heimdall.Milestone, error) { - response, err := heimdallClient.FetchMilestone(ctx, int64(milestoneId)) - - if err != nil { - return 0, nil, err - } - - if err = heimdallStore.Milestones().(interface { - WithTx(kv.Tx) heimdall.EntityStore[*heimdall.Milestone] - }).WithTx(tx).PutEntity(ctx, milestoneId, response); err != nil { - return 0, nil, err - } - - logger.Trace(fmt.Sprintf("[%s] Wrote milestone", logPrefix), "id", milestoneId, "start", response.StartBlock(), "end", response.EndBlock()) - return milestoneId, response, nil -} - -func fetchRequiredHeimdallStateSyncEventsIfNeeded( - ctx context.Context, - header *types.Header, - tx kv.RwTx, - borConfig *borcfg.BorConfig, - blockReader services.FullBlockReader, - heimdallClient heimdall.Client, - bridgeStore bridge.Store, - chainID string, - logPrefix string, - logger log.Logger, - lastStateSyncEventID uint64, - skipCount int, -) (uint64, int, int, time.Duration, error) { - - headerNum := header.Number.Uint64() - if headerNum == 0 || !borConfig.IsSprintStart(headerNum) { - // we fetch events only at beginning of each sprint with blockNum > 0 - return lastStateSyncEventID, 0, skipCount, 0, nil - } - - return fetchAndWriteHeimdallStateSyncEvents( - ctx, - header, - lastStateSyncEventID, - skipCount, - tx, - borConfig, - blockReader, - heimdallClient, - bridgeStore, - chainID, - logPrefix, - logger, - ) -} - -func fetchAndWriteHeimdallStateSyncEvents( - ctx context.Context, - header *types.Header, - lastStateSyncEventID uint64, - skipCount int, - tx kv.RwTx, - config *borcfg.BorConfig, - blockReader services.FullBlockReader, - heimdallClient heimdall.Client, - bridgeStore bridge.Store, - chainID string, - logPrefix string, - logger log.Logger) (uint64, int, int, time.Duration, error) { - fetchStart := time.Now() - // Find out the latest eventId - var fromId uint64 - - blockNum := header.Number.Uint64() - - if blockNum == 0 || !config.IsSprintStart(blockNum) { - // we fetch events only at beginning of each sprint with blockNum > 0 - return lastStateSyncEventID, 0, skipCount, 0, nil - } - - from, to, err := heimdall.CalculateEventWindow(ctx, config, header, tx, blockReader) - - if err != nil { - return lastStateSyncEventID, 0, skipCount, time.Since(fetchStart), err - } - - fetchTo := to - var fetchLimit int - - /* TODO - // we want to get as many historical events as we can in - // each call to heimdall - but we need to make sure we - // don't type to get too recent a sync otherwise it will - // return a nil response - - // to implement this we need to do block processing vs the - // local db to set the blocknu index based on iterating - // the local DB rather than the data received from the - // client - if time.Since(fetchTo) > (30 * time.Minute) { - fetchTo = time.Now().Add(-30 * time.Minute) - fetchLimit = 1000 - } - */ - - fromId = lastStateSyncEventID + 1 - - logger.Trace( - fmt.Sprintf("[%s] Fetching state updates from Heimdall", logPrefix), - "fromId", fromId, - "to", to.Format(time.RFC3339), - ) - - eventRecords, err := heimdallClient.FetchStateSyncEvents(ctx, fromId, fetchTo, fetchLimit) - if err != nil { - return lastStateSyncEventID, 0, skipCount, time.Since(fetchStart), err - } - - var overrideCount int - - if config.OverrideStateSyncRecords != nil { - if val, ok := config.OverrideStateSyncRecords[strconv.FormatUint(blockNum, 10)]; ok { - overrideCount = len(eventRecords) - val //nolint - eventRecords = eventRecords[0:val] - } - } - - var initialRecordTime *time.Time - var lastEventRecord *heimdall.EventRecordWithTime - - for i, eventRecord := range eventRecords { - if eventRecord.ID <= lastStateSyncEventID { - continue - } - - // Note: this check is only valid for events with eventRecord.ID > lastStateSyncEventID - var afterCheck = func(limitTime time.Time, eventTime time.Time, initialTime *time.Time) bool { - if initialTime == nil { - return eventTime.After(from) - } - - return initialTime.After(from) - } - - // don't apply this for devnets we may have looser state event constraints - // (TODO these probably needs fixing) - if skipCount > 0 { - skipCount-- - } else if !(chainID == "1337") { - if lastStateSyncEventID+1 != eventRecord.ID || eventRecord.ChainID != chainID || - !(afterCheck(from, eventRecord.Time, initialRecordTime) && eventRecord.Time.Before(to)) { - return lastStateSyncEventID, i, overrideCount, time.Since(fetchStart), fmt.Errorf( - "invalid event record received %s, %s, %s, %s", - fmt.Sprintf("blockNum=%d", blockNum), - fmt.Sprintf("eventId=%d (exp %d)", eventRecord.ID, lastStateSyncEventID+1), - fmt.Sprintf("chainId=%s (exp %s)", eventRecord.ChainID, chainID), - fmt.Sprintf("time=%s (exp from %s, to %s)", eventRecord.Time, from, to), - ) - } - } - - if initialRecordTime == nil { - eventTime := eventRecord.Time - initialRecordTime = &eventTime - } - - lastStateSyncEventID++ - lastEventRecord = eventRecord - } - - skipCount += overrideCount - - if len(eventRecords) > 0 { - store := bridgeStore.(interface { - WithTx(kv.Tx) bridge.Store - }).WithTx(tx) - - if err := store.PutEvents(ctx, eventRecords); err != nil { - return lastStateSyncEventID, 0, 0, time.Since(fetchStart), err - } - - if lastEventRecord != nil { - logger.Debug("putting state sync events", "blockNum", blockNum, "lastID", lastEventRecord.ID) - if err = store.PutBlockNumToEventId(ctx, map[uint64]uint64{blockNum: lastEventRecord.ID}); err != nil { - return lastStateSyncEventID, len(eventRecords), skipCount, time.Since(fetchStart), err - } - } - } - - return lastStateSyncEventID, len(eventRecords), skipCount, time.Since(fetchStart), nil -} diff --git a/eth/stagedsync/default_stages.go b/eth/stagedsync/default_stages.go index af1b59ec681..a75f89e9142 100644 --- a/eth/stagedsync/default_stages.go +++ b/eth/stagedsync/default_stages.go @@ -30,7 +30,6 @@ import ( func DefaultStages(ctx context.Context, snapshots SnapshotsCfg, headers HeadersCfg, - borHeimdallCfg BorHeimdallCfg, blockHashCfg BlockHashesCfg, bodies BodiesCfg, senders SendersCfg, @@ -71,22 +70,6 @@ func DefaultStages(ctx context.Context, return nil }, }, - { - ID: stages.BorHeimdall, - Description: "Download Bor-specific data from Heimdall", - Forward: func(badBlockUnwind bool, s *StageState, u Unwinder, txc wrap.TxContainer, logger log.Logger) error { - if badBlockUnwind { - return nil - } - return BorHeimdallForward(s, u, ctx, txc.Tx, borHeimdallCfg, logger) - }, - Unwind: func(u *UnwindState, s *StageState, txc wrap.TxContainer, logger log.Logger) error { - return BorHeimdallUnwind(u, ctx, s, txc.Tx, borHeimdallCfg) - }, - Prune: func(p *PruneState, tx kv.RwTx, logger log.Logger) error { - return nil - }, - }, { ID: stages.BlockHashes, Description: "Write block hashes", @@ -569,7 +552,6 @@ func DownloadSyncStages( var DefaultForwardOrder = UnwindOrder{ stages.Snapshots, stages.Headers, - stages.BorHeimdall, stages.BlockHashes, stages.Bodies, @@ -598,7 +580,6 @@ var DefaultUnwindOrder = UnwindOrder{ stages.Bodies, stages.BlockHashes, - stages.BorHeimdall, stages.Headers, } @@ -637,7 +618,6 @@ var DefaultPruneOrder = PruneOrder{ stages.Bodies, stages.BlockHashes, - stages.BorHeimdall, stages.Headers, stages.Snapshots, } diff --git a/eth/stagedsync/exec3.go b/eth/stagedsync/exec3.go index 9233fdc21f3..179ca10645d 100644 --- a/eth/stagedsync/exec3.go +++ b/eth/stagedsync/exec3.go @@ -43,7 +43,6 @@ import ( "github.com/erigontech/erigon/core" "github.com/erigontech/erigon/core/rawdb" "github.com/erigontech/erigon/core/rawdb/rawdbhelpers" - "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" "github.com/erigontech/erigon/core/state" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/eth/ethconfig/estimate" @@ -140,7 +139,7 @@ func (p *Progress) Log(suffix string, rs *state.StateV3, in *state.QueueWithRetr func restoreTxNum(ctx context.Context, cfg *ExecuteBlockCfg, applyTx kv.Tx, doms *state2.SharedDomains, maxBlockNum uint64) ( inputTxNum uint64, maxTxNum uint64, offsetFromBlockBeginning uint64, err error) { - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, cfg.blockReader)) inputTxNum = doms.TxNum() @@ -155,7 +154,7 @@ func restoreTxNum(ctx context.Context, cfg *ExecuteBlockCfg, applyTx kv.Tx, doms return 0, 0, 0, err } - ok, _blockNum, err := txNumsReader.FindBlockNum(applyTx, doms.TxNum()) + _blockNum, ok, err := txNumsReader.FindBlockNum(applyTx, doms.TxNum()) if err != nil { return 0, 0, 0, err } @@ -536,7 +535,6 @@ Loop: // Thus, we need to skip the first txs in the block, however, this causes the GasUsed to be incorrect. // So we skip that check for the first block, if we find half-executed data. skipPostEvaluation := false - var usedGas uint64 var systemTxIndex int var txTasks []*state.TxTask @@ -566,12 +564,6 @@ Loop: Config: chainConfig, } - if txTask.HistoryExecution && usedGas == 0 { - usedGas, _, _, err = rawtemporaldb.ReceiptAsOf(executor.tx().(kv.TemporalTx), txTask.TxNum) - if err != nil { - return err - } - } if txIndex >= 0 && !txTask.Final && isPoSa { if isSystemTx, err := posa.IsSystemTransaction(txs[txIndex], header); err != nil { diff --git a/eth/stagedsync/exec3_serial.go b/eth/stagedsync/exec3_serial.go index f9d71d271d2..d02bc7b1ba3 100644 --- a/eth/stagedsync/exec3_serial.go +++ b/eth/stagedsync/exec3_serial.go @@ -112,14 +112,11 @@ func (se *serialExecutor) execute(ctx context.Context, tasks []*state.TxTask, gp return false, nil } + var receipt *types.Receipt if !txTask.Final { - var receipt *types.Receipt if txTask.TxIndex >= 0 { receipt = txTask.BlockReceipts[txTask.TxIndex] } - if err := rawtemporaldb.AppendReceipt(se.doms, receipt, se.blobGasUsed); err != nil { - return false, err - } } else { if se.cfg.polygonExtraReceipt && se.cfg.chainConfig.Bor != nil && txTask.TxIndex >= 1 { // get last receipt and store the last log index + 1 @@ -129,16 +126,16 @@ func (se *serialExecutor) execute(ctx context.Context, tasks []*state.TxTask, gp } if len(lastReceipt.Logs) > 0 { firstIndex := lastReceipt.Logs[len(lastReceipt.Logs)-1].Index + 1 - receipt := types.Receipt{ + receipt = &types.Receipt{ CumulativeGasUsed: lastReceipt.CumulativeGasUsed, FirstLogIndexWithinBlock: uint32(firstIndex), } - if err := rawtemporaldb.AppendReceipt(se.doms, &receipt, se.blobGasUsed); err != nil { - return false, err - } } } } + if err := rawtemporaldb.AppendReceipt(se.doms, receipt, se.blobGasUsed); err != nil { + return false, err + } // MA applystate if err := se.rs.ApplyState4(ctx, txTask); err != nil { diff --git a/eth/stagedsync/stage_bodies.go b/eth/stagedsync/stage_bodies.go index 6dfe5f9b12e..f5b06ee0316 100644 --- a/eth/stagedsync/stage_bodies.go +++ b/eth/stagedsync/stage_bodies.go @@ -107,13 +107,6 @@ func BodiesForward(s *StageState, u Unwinder, ctx context.Context, tx kv.RwTx, c defer cfg.bd.ClearBodyCache() var headerProgress, bodyProgress uint64 - if cfg.chanConfig.Bor != nil { - headerProgress, err = stages.GetStageProgress(tx, stages.BorHeimdall) - if err != nil { - return err - } - } - if headerProgress == 0 { headerProgress, err = stages.GetStageProgress(tx, stages.Headers) if err != nil { diff --git a/eth/stagedsync/stage_bor_heimdall.go b/eth/stagedsync/stage_bor_heimdall.go deleted file mode 100644 index 91133bae58b..00000000000 --- a/eth/stagedsync/stage_bor_heimdall.go +++ /dev/null @@ -1,890 +0,0 @@ -// Copyright 2024 The Erigon Authors -// This file is part of Erigon. -// -// Erigon 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. -// -// Erigon 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 Erigon. If not, see . - -package stagedsync - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "sort" - "time" - - lru "github.com/hashicorp/golang-lru/arc/v2" - "golang.org/x/sync/errgroup" - - "github.com/erigontech/erigon-lib/chain" - libcommon "github.com/erigontech/erigon-lib/common" - "github.com/erigontech/erigon-lib/common/dbg" - "github.com/erigontech/erigon-lib/kv" - "github.com/erigontech/erigon-lib/log/v3" - "github.com/erigontech/erigon/consensus" - "github.com/erigontech/erigon/core/types" - "github.com/erigontech/erigon/dataflow" - "github.com/erigontech/erigon/eth/ethconfig/estimate" - "github.com/erigontech/erigon/eth/stagedsync/stages" - "github.com/erigontech/erigon/polygon/bor" - "github.com/erigontech/erigon/polygon/bor/borcfg" - "github.com/erigontech/erigon/polygon/bor/bordb" - "github.com/erigontech/erigon/polygon/bor/finality" - "github.com/erigontech/erigon/polygon/bor/finality/whitelist" - "github.com/erigontech/erigon/polygon/bor/valset" - "github.com/erigontech/erigon/polygon/bridge" - "github.com/erigontech/erigon/polygon/heimdall" - "github.com/erigontech/erigon/polygon/sync" - "github.com/erigontech/erigon/turbo/services" - "github.com/erigontech/erigon/turbo/stages/headerdownload" -) - -const ( - inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory - snapshotPersistInterval = 1024 // Number of blocks after which to persist the vote snapshot to the database -) - -type BorHeimdallCfg struct { - db kv.RwDB - snapDb kv.RwDB // Database to store and retrieve snapshot checkpoints - miningState *MiningState - chainConfig *chain.Config - borConfig *borcfg.BorConfig - heimdallClient heimdall.Client - heimdallStore heimdall.Store - bridgeStore bridge.Store - blockReader services.FullBlockReader - hd *headerdownload.HeaderDownload - penalize func(context.Context, []headerdownload.PenaltyItem) - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot] - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address] - recordWaypoints bool - unwindCfg bordb.HeimdallUnwindCfg -} - -func StageBorHeimdallCfg( - db kv.RwDB, - snapDb kv.RwDB, - miningState MiningState, - chainConfig chain.Config, - heimdallClient heimdall.Client, - heimdallStore heimdall.Store, - bridgeStore bridge.Store, - blockReader services.FullBlockReader, - hd *headerdownload.HeaderDownload, - penalize func(context.Context, []headerdownload.PenaltyItem), - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot], - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address], - recordWaypoints bool, - userUnwindTypeOverrides []string, -) BorHeimdallCfg { - var borConfig *borcfg.BorConfig - if chainConfig.Bor != nil { - borConfig = chainConfig.Bor.(*borcfg.BorConfig) - } - - unwindCfg := bordb.HeimdallUnwindCfg{} // unwind everything by default - if len(userUnwindTypeOverrides) > 0 { - unwindCfg.ApplyUserUnwindTypeOverrides(userUnwindTypeOverrides) - } - - return BorHeimdallCfg{ - db: db, - snapDb: snapDb, - miningState: &miningState, - chainConfig: &chainConfig, - borConfig: borConfig, - heimdallClient: heimdallClient, - heimdallStore: heimdallStore, - bridgeStore: bridgeStore, - blockReader: blockReader, - hd: hd, - penalize: penalize, - recents: recents, - signatures: signatures, - recordWaypoints: recordWaypoints, - unwindCfg: unwindCfg, - } -} - -func BorHeimdallForward( - s *StageState, - u Unwinder, - ctx context.Context, - tx kv.RwTx, - cfg BorHeimdallCfg, - logger log.Logger, -) (err error) { - processStart := time.Now() - if cfg.borConfig == nil || cfg.heimdallClient == nil { - return - } - - useExternalTx := tx != nil - if !useExternalTx { - var err error - tx, err = cfg.db.BeginRw(ctx) - if err != nil { - return err - } - defer tx.Rollback() - } - - headNumber, err := stages.GetStageProgress(tx, stages.Headers) - if err != nil { - return err - } - - whitelistService := whitelist.GetWhitelistingService() - if unwindPointPtr := finality.BorMilestoneRewind.Load(); unwindPointPtr != nil && *unwindPointPtr != 0 { - unwindPoint := *unwindPointPtr - if whitelistService != nil && unwindPoint < headNumber { - header, err := cfg.blockReader.HeaderByNumber(ctx, tx, headNumber) - if err != nil { - return err - } - - hash := header.Hash() - logger.Debug( - fmt.Sprintf("[%s] Verification failed for header due to milestone rewind", s.LogPrefix()), - "hash", hash, - "height", headNumber, - ) - cfg.penalize(ctx, []headerdownload.PenaltyItem{{ - Penalty: headerdownload.BadBlockPenalty, - PeerID: cfg.hd.SourcePeerId(hash), - }}) - dataflow.HeaderDownloadStates.AddChange(headNumber, dataflow.HeaderInvalidated) - if err := s.state.UnwindTo(unwindPoint, ForkReset(hash), tx); err != nil { - return err - } - var reset uint64 = 0 - finality.BorMilestoneRewind.Store(&reset) - return fmt.Errorf("verification failed for header %d: %x", headNumber, header.Hash()) - } - } - - if s.BlockNumber == headNumber { - return nil - } - - lastBlockNum := s.BlockNumber - if cfg.blockReader.FrozenBorBlocks() > lastBlockNum { - lastBlockNum = cfg.blockReader.FrozenBorBlocks() - } - - recents, err := lru.NewARC[libcommon.Hash, *bor.Snapshot](inmemorySnapshots) - if err != nil { - return err - } - - signatures, err := lru.NewARC[libcommon.Hash, libcommon.Address](sync.InMemorySignatures) - if err != nil { - return err - } - - var blockNum uint64 - var fetchTime time.Duration - var snapTime time.Duration - var snapInitTime time.Duration - var syncEventTime time.Duration - - var eventRecords int - - lastSpanID, err := fetchRequiredHeimdallSpansIfNeeded(ctx, headNumber, tx, cfg, s.LogPrefix(), logger) - if err != nil { - return err - } - - var lastCheckpointId, lastMilestoneId uint64 - - var waypointTime time.Duration - - if cfg.recordWaypoints { - waypointStart := time.Now() - - lastCheckpointId, err = fetchAndWriteHeimdallCheckpointsIfNeeded(ctx, headNumber, tx, cfg, s.LogPrefix(), logger) - - if err != nil { - return err - } - - lastMilestoneId, err = fetchAndWriteHeimdallMilestonesIfNeeded(ctx, headNumber, tx, cfg, s.LogPrefix(), logger) - - if err != nil { - return err - } - - waypointTime = waypointTime + time.Since(waypointStart) - } - - lastStateSyncEventID, _, err := cfg.blockReader.LastEventId(ctx, tx) - if err != nil { - return err - } - - chainReader := NewChainReaderImpl(cfg.chainConfig, tx, cfg.blockReader, logger) - logTimer := time.NewTicker(logInterval) - defer logTimer.Stop() - - logger.Info(fmt.Sprintf("[%s] Processing sync events...", s.LogPrefix()), "from", lastBlockNum+1, "to", headNumber) - var nextEventRecord *heimdall.EventRecordWithTime - - // sometimes via config events are skipped from particular blocks and - // pushed into the next one, when this happens we need to skip validation - // as the times won't match the expected window. In practice it only affects - // these blocks: 14949120,14949184, 14953472, 14953536, 14953600, 14953664, - // 14953728, 14953792, 14953856 so it seems keeping a local skip marker is good - // enough - it will only impact sync from origin operations. If - // this becomes more prevalent this will need to be re-thought - var skipCount int - - // allow committing every N blocks to avoid long transaction and potential progress lost when running `./build/bin/integration stage_bor_heimdall` forward operation manually, - // for more details see the use case: https://github.com/erigontech/erigon/pull/12706#issuecomment-2477818677, - // N=1000 is not verified to be most optimal value, but works fine in unit tests - var commitBatchLimit = 1_000 - var commitCnt int - - // newTx==true means a batch has been committed and should init a fresh new tx to handle next batch - newTx := false - for blockNum = lastBlockNum + 1; blockNum <= headNumber; blockNum++ { - if !useExternalTx && newTx { - newTx = false - tx, err = cfg.db.BeginRw(ctx) - if err != nil { - return err - } - defer tx.Rollback() // rollback nil tx is supported - chainReader = NewChainReaderImpl(cfg.chainConfig, tx, cfg.blockReader, logger) - } - select { - default: - case <-logTimer.C: - logger.Info( - fmt.Sprintf("[%s] StateSync Progress", s.LogPrefix()), - "progress", blockNum, - "lastSpanID", lastSpanID, - "lastCheckpointId", lastCheckpointId, - "lastMilestoneId", lastMilestoneId, - "lastStateSyncEventID", lastStateSyncEventID, - "total records", eventRecords, - "sync-events", syncEventTime, - "sync-event-fetch", fetchTime, - "snaps", snapTime, - "snap-init", snapInitTime, - "waypoints", waypointTime, - "process time", time.Since(processStart), - ) - } - - header, err := cfg.blockReader.HeaderByNumber(ctx, tx, blockNum) - if err != nil { - return err - } - if header == nil { - _, _ = cfg.blockReader.HeaderByNumber(dbg.ContextWithDebug(ctx, true), tx, blockNum) - return fmt.Errorf("header not found: %d", blockNum) - } - - // Whitelist whitelistService is called to check if the bor chainReader is - // on the canonical chainReader according to milestones - if whitelistService != nil && !whitelistService.IsValidChain(blockNum, []*types.Header{header}) { - logger.Debug( - fmt.Sprintf("[%s] Verification failed for header", s.LogPrefix()), - "height", blockNum, - "hash", header.Hash(), - ) - - cfg.penalize(ctx, []headerdownload.PenaltyItem{{ - Penalty: headerdownload.BadBlockPenalty, - PeerID: cfg.hd.SourcePeerId(header.Hash()), - }}) - - dataflow.HeaderDownloadStates.AddChange(blockNum, dataflow.HeaderInvalidated) - if err := s.state.UnwindTo(blockNum-1, ForkReset(header.Hash()), tx); err != nil { - return err - } - return fmt.Errorf("verification failed for header %d: %x", blockNum, header.Hash()) - } - - snapStart := time.Now() - - if cfg.blockReader.BorSnapshots().SegmentsMin() == 0 { - snapTime = snapTime + time.Since(snapStart) - // SegmentsMin is only set if running as an uploader process (check SnapshotsCfg.snapshotUploader and - // SegmentsMin is only set if running as an uploader process (check SnapshotsCfg.snapshotUploader and - // UploadLocationFlag) when we remove snapshots based on FrozenBlockLimit and number of uploaded snapshots - // avoid calling this if block for blockNums <= SegmentsMin to avoid reinsertion of snapshots - snap := loadSnapshot(blockNum, header.Hash(), cfg.borConfig, recents, signatures, cfg.snapDb, logger) - - lastPersistedBlockNum, err := lastPersistedSnapshotBlock(cfg.snapDb) - if err != nil { - return err - } - - // this will happen if for example chaindb is removed - if lastPersistedBlockNum > blockNum { - lastPersistedBlockNum = blockNum - } - - // if the last time we persisted snapshots is too far away re-run the forward - // initialization process - this is to avoid memory growth due to recusrion - // in persistValidatorSets - if snap == nil && (blockNum == 1 || blockNum-lastPersistedBlockNum > (snapshotPersistInterval*5)) { - snap, err = initValidatorSets( - ctx, - tx, - cfg.blockReader, - cfg.borConfig, - cfg.heimdallClient, - cfg.heimdallStore, - chainReader, - blockNum, - lastPersistedBlockNum, - recents, - signatures, - cfg.snapDb, - logger, - s.LogPrefix(), - ) - if err != nil { - return fmt.Errorf("can't initialise validator sets: %w", err) - } - } - - snapInitTime = snapInitTime + time.Since(snapStart) - - err = persistValidatorSets( - snap, - u, - tx, - cfg.borConfig, - chainReader, - blockNum, - header.Hash(), - recents, - signatures, - cfg.snapDb, - logger, - s.LogPrefix(), - ) - if err != nil { - return fmt.Errorf("can't persist validator sets: %w", err) - } - } - - snapTime = snapTime + time.Since(snapStart) - - if err := checkBorHeaderExtraDataIfRequired(chainReader, header, cfg.borConfig); err != nil { - return err - } - - snapTime = snapTime + time.Since(snapStart) - - syncEventStart := time.Now() - var callTime time.Duration - - var endStateSyncEventId uint64 - - if nextEventRecord == nil || header.Time > uint64(nextEventRecord.Time.Unix()) { - var records int - - if lastStateSyncEventID == 0 || lastStateSyncEventID != endStateSyncEventId { - lastStateSyncEventID, records, skipCount, callTime, err = fetchRequiredHeimdallStateSyncEventsIfNeeded( - ctx, - header, - tx, - cfg.borConfig, - cfg.blockReader, - cfg.heimdallClient, - cfg.bridgeStore, - cfg.chainConfig.ChainID.String(), - s.LogPrefix(), - logger, - lastStateSyncEventID, - skipCount, - ) - - if err != nil { - return err - } - } - - if records != 0 { - nextEventRecord = nil - eventRecords += records - } else { - if nextEventRecord == nil || nextEventRecord.ID <= lastStateSyncEventID { - if eventRecord, err := cfg.heimdallClient.FetchStateSyncEvent(ctx, lastStateSyncEventID+1); err == nil { - nextEventRecord = eventRecord - endStateSyncEventId = 0 - } else { - if !errors.Is(err, heimdall.ErrEventRecordNotFound) { - return err - } - endStateSyncEventId = lastStateSyncEventID - } - } - } - } - - fetchTime += callTime - syncEventTime = syncEventTime + time.Since(syncEventStart) - - commitCnt++ - if !useExternalTx { - if commitCnt >= commitBatchLimit || blockNum == headNumber { - if err = s.Update(tx, blockNum); err != nil { - return err - } - lastStateSyncEventID, _, _ = cfg.blockReader.LastEventId(ctx, tx) - if err = tx.Commit(); err != nil { - return err - } - commitCnt = 0 - newTx = true - } - } - } - - logger.Info( - fmt.Sprintf("[%s] Sync events", s.LogPrefix()), - "progress", blockNum-1, - "lastSpanID", lastSpanID, - "lastSpanID", lastSpanID, - "lastCheckpointId", lastCheckpointId, - "lastMilestoneId", lastMilestoneId, - "lastStateSyncEventID", lastStateSyncEventID, - "total records", eventRecords, - "sync event time", syncEventTime, - "fetch time", fetchTime, - "snap time", snapTime, - "waypoint time", waypointTime, - "process time", time.Since(processStart), - ) - return -} - -func loadSnapshot( - blockNum uint64, - hash libcommon.Hash, - config *borcfg.BorConfig, - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot], - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address], - snapDb kv.RwDB, - logger log.Logger, -) *bor.Snapshot { - - if s, ok := recents.Get(hash); ok { - return s - } - - if blockNum%snapshotPersistInterval == 0 { - if s, err := bor.LoadSnapshot(config, signatures, snapDb, hash); err == nil { - logger.Trace("Loaded snapshot from disk", "number", blockNum, "hash", hash) - return s - } - } - - return nil -} - -func persistValidatorSets( - snap *bor.Snapshot, - u Unwinder, - chainDBTx kv.Tx, - config *borcfg.BorConfig, - chain consensus.ChainHeaderReader, - blockNum uint64, - hash libcommon.Hash, - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot], - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address], - snapDb kv.RwDB, - logger log.Logger, - logPrefix string, -) error { - - logEvery := time.NewTicker(logInterval) - defer logEvery.Stop() - // Search for a snapshot in memory or on disk for checkpoints - - headers := make([]*types.Header, 0, 16) - var parent *types.Header - - if s, ok := recents.Get(hash); ok { - snap = s - } - - count := 0 - dbsize := uint64(0) - - //nolint:govet - for snap == nil { - // If an on-disk snapshot can be found, use that - if blockNum%snapshotPersistInterval == 0 { - if s, err := bor.LoadSnapshot(config, signatures, snapDb, hash); err == nil { - logger.Trace("Loaded snapshot from disk", "number", blockNum, "hash", hash) - - snap = s - - break - } - } - - // No snapshot for this header, gather the header and move backward - var header *types.Header - // No explicit parents (or no more left), reach out to the database - if parent != nil { - header = parent - } else if chain != nil { - header = chain.GetHeader(hash, blockNum) - //logger.Info(fmt.Sprintf("header %d %x => %+v\n", header.Number.Uint64(), header.Hash(), header)) - } - - if header == nil { - return consensus.ErrUnknownAncestor - } - - if blockNum == 0 { - break - } - - headers = append(headers, header) - blockNum, hash = blockNum-1, header.ParentHash - if chain != nil { - parent = chain.GetHeader(hash, blockNum) - } - - // If an in-memory snapshot was found, use that - if s, ok := recents.Get(hash); ok { - snap = s - break - } - - select { - case <-logEvery.C: - if dbsize == 0 { - _ = snapDb.View(context.Background(), func(tx kv.Tx) error { - dbsize, _ = tx.Count(kv.BorSeparate) - return nil - }) - } - logger.Info( - fmt.Sprintf("[%s] Gathering headers for validator proposer prorities (backwards)", logPrefix), - "processed", count, "blockNum", blockNum, "dbsize", dbsize, - ) - default: - } - - count++ - } - - // check if snapshot is nil - if snap == nil { - return fmt.Errorf("unknown error while retrieving snapshot at block number %v", blockNum) - } - - // Previous snapshot found, apply any pending headers on top of it - for i := 0; i < len(headers)/2; i++ { - headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] - } - - if len(headers) > 0 { - var err error - if snap, err = snap.Apply(parent, headers, logger); err != nil { - if snap != nil { - var badHash libcommon.Hash - for _, header := range headers { - if header.Number.Uint64() == snap.Number+1 { - badHash = header.Hash() - break - } - } - if err := u.UnwindTo(snap.Number, BadBlock(badHash, err), chainDBTx); err != nil { - return err - } - } else { - return fmt.Errorf( - "snap.Apply %d, headers %d-%d: %w", - blockNum, - headers[0].Number.Uint64(), - headers[len(headers)-1].Number.Uint64(), - err, - ) - } - } - } - - recents.Add(snap.Hash, snap) - - // If we've generated a new persistent snapshot, save to disk - if snap.Number%snapshotPersistInterval == 0 && len(headers) > 0 { - if err := snap.Store(snapDb); err != nil { - return fmt.Errorf("snap.Store: %w", err) - } - - logger.Debug( - fmt.Sprintf("[%s] Stored proposer snapshot to disk (persist)", logPrefix), - "number", snap.Number, - "hash", snap.Hash, - ) - } - - return nil -} - -func lastPersistedSnapshotBlock(snapDb kv.RwDB) (uint64, error) { - var lastPersistedBlockNum uint64 - - err := snapDb.View(context.Background(), func(tx kv.Tx) error { - progressBytes, err := tx.GetOne(kv.BorSeparate, []byte("bor-snapshot-progress")) - if err != nil { - return err - } - - if len(progressBytes) == 8 { - lastPersistedBlockNum = binary.BigEndian.Uint64(progressBytes) - } - - return nil - }) - - return lastPersistedBlockNum, err -} - -func initValidatorSets( - ctx context.Context, - tx kv.RwTx, - blockReader services.FullBlockReader, - config *borcfg.BorConfig, - heimdallClient heimdall.Client, - heimdallStore heimdall.Store, - chain consensus.ChainHeaderReader, - blockNum uint64, - lastPersistedBlockNum uint64, - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot], - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address], - snapDb kv.RwDB, - logger log.Logger, - logPrefix string, -) (*bor.Snapshot, error) { - - logEvery := time.NewTicker(logInterval) - defer logEvery.Stop() - - var snap *bor.Snapshot - - var parentHeader *types.Header - var firstBlockNum uint64 - - if lastPersistedBlockNum > 0 { - parentHeader = chain.GetHeaderByNumber(lastPersistedBlockNum) - if parentHeader == nil { - return nil, fmt.Errorf("[%s] header not found: %d", logPrefix, lastPersistedBlockNum) - } - snap = loadSnapshot(lastPersistedBlockNum, parentHeader.Hash(), config, recents, signatures, snapDb, logger) - firstBlockNum = lastPersistedBlockNum + 1 - } else { - // Special handling of the headers in the snapshot - zeroHeader := chain.GetHeaderByNumber(0) - - if zeroHeader != nil { - // get checkpoint data - hash := zeroHeader.Hash() - - if snap = loadSnapshot(0, hash, config, recents, signatures, snapDb, logger); snap == nil { - // get validators and current span - zeroSpan, _, err := blockReader.Span(ctx, tx, 0) - - if err != nil { - if _, err := fetchAndWriteHeimdallSpan(ctx, 0, tx, heimdallClient, heimdallStore, logPrefix, logger); err != nil { - return nil, err - } - - zeroSpan, _, err = blockReader.Span(ctx, tx, 0) - - if err != nil { - return nil, err - } - } - - if zeroSpan == nil { - return nil, errors.New("zero span not found") - } - - // new snap shot - snap = bor.NewSnapshot(config, signatures, 0, hash, zeroSpan.ValidatorSet.Validators, logger) - if err := snap.Store(snapDb); err != nil { - return nil, fmt.Errorf("snap.Store (0): %w", err) - } - - logger.Debug(fmt.Sprintf("[%s] Stored proposer snapshot to disk (init)", logPrefix), "number", 0, "hash", hash) - } - - parentHeader = zeroHeader - firstBlockNum = 1 - lastPersistedBlockNum = 0 - } - } - - g := errgroup.Group{} - g.SetLimit(estimate.AlmostAllCPUs()) - defer func() { - _ = g.Wait() // goroutines used in this err group do not return err (check below) - }() - - batchSize := 128 // must be < InMemorySignatures - initialHeaders := make([]*types.Header, 0, batchSize) - - var err error - - for i := firstBlockNum; i <= blockNum; i++ { - header := chain.GetHeaderByNumber(i) - { - // `snap.apply` bottleneck - is recover of signer. - // to speedup: recover signer in background goroutines and save in `sigcache` - // `batchSize` < `InMemorySignatures`: means all current batch will fit in cache - and - // `snap.apply` will find it there. - g.Go(func() error { - if header == nil { - return nil - } - _, _ = bor.Ecrecover(header, signatures, config) - return nil - }) - } - if header == nil { - return nil, fmt.Errorf("missing header persisting validator sets: (inside loop at %d)", i) - } - initialHeaders = append(initialHeaders, header) - - if len(initialHeaders) == cap(initialHeaders) { - - if snap, err = snap.Apply(parentHeader, initialHeaders, logger); err != nil { - return nil, fmt.Errorf("snap.Apply (inside loop): %w", err) - } - - parentHeader = initialHeaders[len(initialHeaders)-1] - initialHeaders = initialHeaders[:0] - - // If we've generated a new persistent snapshot, save to disk - if snap.Number%snapshotPersistInterval == 0 { - if err := snap.Store(snapDb); err != nil { - return nil, fmt.Errorf("snap.Store: %w", err) - } - - lastPersistedBlockNum = snap.Number - - logger.Trace( - fmt.Sprintf("[%s] Stored proposer snapshot to disk (init loop)", logPrefix), - "number", snap.Number, - "hash", snap.Hash, - ) - } - } - - select { - case <-logEvery.C: - logger.Info(fmt.Sprintf("[%s] Computing validator proposer prorities (forward)", logPrefix), "to", blockNum, "snapNum", i, "persisted", lastPersistedBlockNum) - default: - } - } - - if snap, err = snap.Apply(parentHeader, initialHeaders, logger); err != nil { - return nil, fmt.Errorf("snap.Apply (outside loop): %w", err) - } - - return snap, nil -} - -func checkBorHeaderExtraDataIfRequired(chr chainHeaderReader, header *types.Header, cfg *borcfg.BorConfig) error { - if !cfg.IsSprintEnd(header.Number.Uint64()) { - // not last block of a sprint in a span, so no check needed (we only check last block of a sprint) - return nil - } - - return checkBorHeaderExtraData(chr, header, cfg) -} - -type chainHeaderReader interface { - // bor span with given ID - BorSpan(spanId uint64) *heimdall.Span -} - -func checkBorHeaderExtraData(chr chainHeaderReader, header *types.Header, cfg *borcfg.BorConfig) error { - spanID := heimdall.SpanIdAt(header.Number.Uint64() + 1) - sp := chr.BorSpan(uint64(spanID)) - - producerSet := make([]*valset.Validator, len(sp.SelectedProducers)) - for i := range sp.SelectedProducers { - producerSet[i] = &sp.SelectedProducers[i] - } - - sort.Sort(valset.ValidatorsByAddress(producerSet)) - - headerVals, err := valset.ParseValidators(bor.GetValidatorBytes(header, cfg)) - if err != nil { - return err - } - - // span 0 at least for mumbai has a header mismatch in - // its first spam. Since we control neither the span, not the - // the headers (they are external data) - we just don't do the - // check as it will hault further processing - if len(producerSet) != len(headerVals) && spanID > 0 { - return ErrHeaderValidatorsLengthMismatch - } - - for i, val := range producerSet { - if !bytes.Equal(val.HeaderBytes(), headerVals[i].HeaderBytes()) { - return ErrHeaderValidatorsBytesMismatch - } - } - - return nil -} - -func BorHeimdallUnwind(u *UnwindState, ctx context.Context, _ *StageState, tx kv.RwTx, cfg BorHeimdallCfg) (err error) { - u.UnwindPoint = max(u.UnwindPoint, cfg.blockReader.FrozenBorBlocks()) // protect from unwind behind files - - if cfg.borConfig == nil { - return - } - - useExternalTx := tx != nil - if !useExternalTx { - tx, err = cfg.db.BeginRw(ctx) - if err != nil { - return err - } - defer tx.Rollback() - } - - if err = bordb.UnwindHeimdall(ctx, cfg.heimdallStore, cfg.bridgeStore, tx, u.UnwindPoint, cfg.unwindCfg); err != nil { - return err - } - - if err = u.Done(tx); err != nil { - return err - } - - if !useExternalTx { - if err = tx.Commit(); err != nil { - return err - } - } - - return -} diff --git a/eth/stagedsync/stage_bor_heimdall_test.go b/eth/stagedsync/stage_bor_heimdall_test.go deleted file mode 100644 index da999da07fa..00000000000 --- a/eth/stagedsync/stage_bor_heimdall_test.go +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2024 The Erigon Authors -// This file is part of Erigon. -// -// Erigon 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. -// -// Erigon 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 Erigon. If not, see . - -package stagedsync_test - -import ( - "bytes" - "context" - "errors" - "math/big" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/erigontech/erigon-lib/crypto" - "github.com/erigontech/erigon-lib/log/v3" - "github.com/erigontech/erigon/core" - "github.com/erigontech/erigon/core/types" - "github.com/erigontech/erigon/eth/stagedsync" - "github.com/erigontech/erigon/eth/stagedsync/stagedsynctest" - "github.com/erigontech/erigon/eth/stagedsync/stages" - "github.com/erigontech/erigon/polygon/bor" - "github.com/erigontech/erigon/polygon/bor/valset" - "github.com/erigontech/erigon/polygon/heimdall" -) - -func TestBorHeimdallForwardPersistsSpans(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 4000 - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForward(t, stages.BorHeimdall) - - // asserts - spans, err := testHarness.ReadSpansFromDB(ctx) - require.NoError(t, err) - require.Len(t, spans, 2) - require.Equal(t, heimdall.SpanId(0), spans[0].Id) - require.Equal(t, uint64(0), spans[0].StartBlock) - require.Equal(t, uint64(255), spans[0].EndBlock) - require.Equal(t, heimdall.SpanId(1), spans[1].Id) - require.Equal(t, uint64(256), spans[1].StartBlock) - require.Equal(t, uint64(6655), spans[1].EndBlock) -} - -func TestBorHeimdallForwardFetchesFirstSpanDuringSecondSprintStart(t *testing.T) { - // span 0 and 1 are required in the start of second sprint (of 0th span) to commit - // in genesis contracts. we need span 1 at that time to mimic behaviour in bor. - t.Parallel() - - ctx := context.Background() - numBlocks := 16 // Start of 2nd sprint of 0th span - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForward(t, stages.BorHeimdall) - - // asserts - spans, err := testHarness.ReadSpansFromDB(ctx) - require.NoError(t, err) - require.Len(t, spans, 2) - require.Equal(t, heimdall.SpanId(0), spans[0].Id) - require.Equal(t, uint64(0), spans[0].StartBlock) - require.Equal(t, uint64(255), spans[0].EndBlock) - require.Equal(t, heimdall.SpanId(1), spans[1].Id) - require.Equal(t, uint64(256), spans[1].StartBlock) - require.Equal(t, uint64(6655), spans[1].EndBlock) -} - -func TestBorHeimdallForwardFetchesFirstSpanAfterSecondSprintStart(t *testing.T) { - // Note this test differs from TestBorHeimdallForwardFetchesFirstSpanDuringSecondSprintStart - // since we should be able to handle both scenarios: - // - calling the stage with toBlockNum=16 - // - calling the stage with toBlockNum=20 (some block num after second sprint start) - // - // span 0 and 1 are required at and after the start of second sprint (of 0th span) to commit - // in genesis contracts. we need span 1 at that time to mimic behaviour in bor. - t.Parallel() - - ctx := context.Background() - numBlocks := 20 // After the start of 2nd sprint of 0th span - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForward(t, stages.BorHeimdall) - - // asserts - spans, err := testHarness.ReadSpansFromDB(ctx) - require.NoError(t, err) - require.Len(t, spans, 2) - require.Equal(t, heimdall.SpanId(0), spans[0].Id) - require.Equal(t, uint64(0), spans[0].StartBlock) - require.Equal(t, uint64(255), spans[0].EndBlock) - require.Equal(t, heimdall.SpanId(1), spans[1].Id) - require.Equal(t, uint64(256), spans[1].StartBlock) - require.Equal(t, uint64(6655), spans[1].EndBlock) -} - -func TestBorHeimdallForwardFetchesNextSpanDuringLastSprintOfCurrentSpan(t *testing.T) { - // heimdall prepares the next span a number of sprints before the end of the current one - // we should be fetching the next span once we reach the last sprint of the current span - // this mimics the behaviour in bor - t.Parallel() - - ctx := context.Background() - numBlocks := 6640 - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForward(t, stages.BorHeimdall) - - // asserts - spans, err := testHarness.ReadSpansFromDB(ctx) - require.NoError(t, err) - require.Len(t, spans, 3) - require.Equal(t, heimdall.SpanId(0), spans[0].Id) - require.Equal(t, uint64(0), spans[0].StartBlock) - require.Equal(t, uint64(255), spans[0].EndBlock) - require.Equal(t, heimdall.SpanId(1), spans[1].Id) - require.Equal(t, uint64(256), spans[1].StartBlock) - require.Equal(t, uint64(6655), spans[1].EndBlock) - require.Equal(t, heimdall.SpanId(2), spans[2].Id) - require.Equal(t, uint64(6656), spans[2].StartBlock) - require.Equal(t, uint64(13055), spans[2].EndBlock) -} - -func TestBorHeimdallForwardPersistsStateSyncEvents(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 96 - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForward(t, stages.BorHeimdall) - - // asserts - // 1 event per sprint expected - events, err := testHarness.ReadStateSyncEventsFromDB(ctx) - require.NoError(t, err) - require.Len(t, events, 6) - - lastEventNumPerBlock, err := testHarness.ReadLastStateSyncEventNumPerBlockFromDB(ctx) - require.NoError(t, err) - require.Len(t, lastEventNumPerBlock, 6) - require.Equal(t, uint64(1), lastEventNumPerBlock[16]) - require.Equal(t, uint64(2), lastEventNumPerBlock[32]) - require.Equal(t, uint64(3), lastEventNumPerBlock[48]) - require.Equal(t, uint64(4), lastEventNumPerBlock[64]) - require.Equal(t, uint64(5), lastEventNumPerBlock[80]) - require.Equal(t, uint64(6), lastEventNumPerBlock[96]) -} - -func TestBorHeimdallForwardErrHeaderValidatorsLengthMismatch(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 271 - validatorKey1, err := crypto.GenerateKey() - require.NoError(t, err) - validatorKey2, err := crypto.GenerateKey() - require.NoError(t, err) - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - HeimdallProducersOverride: map[uint64][]valset.Validator{ - 1: { - *valset.NewValidator(crypto.PubkeyToAddress(validatorKey1.PublicKey), 1), - *valset.NewValidator(crypto.PubkeyToAddress(validatorKey2.PublicKey), 1), - }, - }, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForwardWithErrorIs(t, stages.BorHeimdall, stagedsync.ErrHeaderValidatorsLengthMismatch) -} - -func TestBorHeimdallForwardErrHeaderValidatorsBytesMismatch(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 271 - validatorKey1, err := crypto.GenerateKey() - require.NoError(t, err) - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - HeimdallProducersOverride: map[uint64][]valset.Validator{ - 1: { - *valset.NewValidator(crypto.PubkeyToAddress(validatorKey1.PublicKey), 1), - }, - }, - }) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunStateSyncStageForwardWithErrorIs(t, stages.BorHeimdall, stagedsync.ErrHeaderValidatorsBytesMismatch) -} - -func TestBorHeimdallForwardDetectsUnauthorizedSignerError(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 312 - chainConfig := stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays() - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: chainConfig, - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - - // prepare invalid header and insert it in the db - latestHeader, err := testHarness.ReadHeaderByNumber(ctx, uint64(numBlocks)) - require.NoError(t, err) - gasLimit := uint64(15500) - invalidHeader := core.MakeEmptyHeader(latestHeader, chainConfig, uint64(time.Now().Unix()), &gasLimit) - invalidHeader.Number = new(big.Int).Add(latestHeader.Number, big.NewInt(1)) - invalidHeader.Extra = bytes.Repeat([]byte{0x00}, types.ExtraVanityLength+types.ExtraSealLength) - validatorKey1, err := crypto.GenerateKey() - require.NoError(t, err) - sighash, err := crypto.Sign(crypto.Keccak256(bor.BorRLP(invalidHeader, testHarness.BorConfig())), validatorKey1) - require.NoError(t, err) - copy(invalidHeader.Extra[len(invalidHeader.Extra)-types.ExtraSealLength:], sighash) - testHarness.SaveHeader(ctx, t, invalidHeader) - // pretend-update previous stage progress - testHarness.SaveStageProgress(ctx, t, stages.Headers, invalidHeader.Number.Uint64()) - require.Equal(t, uint64(numBlocks+1), testHarness.GetStageProgress(ctx, t, stages.Headers)) - require.Equal(t, uint64(0), testHarness.GetStageProgress(ctx, t, stages.BorHeimdall)) - - // run stage under test - testHarness.RunStateSyncStageForward(t, stages.BorHeimdall) - - // asserts - require.Equal(t, uint64(numBlocks+1), testHarness.GetStageProgress(ctx, t, stages.BorHeimdall)) - require.Equal(t, invalidHeader.Number.Uint64()-1, testHarness.StateSyncUnwindPoint()) - unwindReason := testHarness.StateSyncUnwindReason() - require.Equal(t, invalidHeader.Hash(), *unwindReason.Block) - var unauthorizedSignerErr *valset.UnauthorizedSignerError - ok := errors.As(unwindReason.Err, &unauthorizedSignerErr) - require.True(t, ok) - require.Equal(t, invalidHeader.Number.Uint64(), unauthorizedSignerErr.Number) - require.Equal(t, crypto.PubkeyToAddress(validatorKey1.PublicKey).Bytes(), unauthorizedSignerErr.Signer) -} diff --git a/eth/stagedsync/stage_commit_rebuild.go b/eth/stagedsync/stage_commit_rebuild.go index 2bb0d6696af..dd44bc098d9 100644 --- a/eth/stagedsync/stage_commit_rebuild.go +++ b/eth/stagedsync/stage_commit_rebuild.go @@ -63,7 +63,7 @@ func StageTrieCfg(db kv.RwDB, checkRoot, saveNewHashesToDB, badBlockHalt bool, t var ErrInvalidStateRootHash = errors.New("invalid state root hash") func RebuildPatriciaTrieBasedOnFiles(ctx context.Context, cfg TrieCfg) (libcommon.Hash, error) { - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, cfg.blockReader)) rh, err := cfg.agg.RebuildCommitmentFiles(ctx, cfg.db, &txNumsReader) if err != nil { return trie.EmptyRoot, err diff --git a/eth/stagedsync/stage_custom_trace.go b/eth/stagedsync/stage_custom_trace.go index 11928e44595..8cf7cab0480 100644 --- a/eth/stagedsync/stage_custom_trace.go +++ b/eth/stagedsync/stage_custom_trace.go @@ -26,11 +26,11 @@ import ( "time" "github.com/erigontech/erigon-lib/chain" - libcommon "github.com/erigontech/erigon-lib/common" - "github.com/erigontech/erigon-lib/common/assert" + "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/common/datadir" "github.com/erigontech/erigon-lib/common/dbg" "github.com/erigontech/erigon-lib/kv" + "github.com/erigontech/erigon-lib/kv/backup" "github.com/erigontech/erigon-lib/kv/rawdbv3" "github.com/erigontech/erigon-lib/log/v3" state2 "github.com/erigontech/erigon-lib/state" @@ -40,8 +40,9 @@ import ( "github.com/erigontech/erigon/core/state" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/eth/ethconfig" + "github.com/erigontech/erigon/eth/integrity" "github.com/erigontech/erigon/eth/stagedsync/stages" - exec4 "github.com/erigontech/erigon/execution/exec3" + "github.com/erigontech/erigon/execution/exec3" "github.com/erigontech/erigon/turbo/services" "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" ) @@ -49,10 +50,10 @@ import ( type CustomTraceCfg struct { tmpdir string db kv.TemporalRwDB - ExecArgs *exec4.ExecArgs - Produce Produce -} + ExecArgs *exec3.ExecArgs + Produce Produce +} type Produce struct { ReceiptDomain bool RCacheDomain bool @@ -86,8 +87,8 @@ func NewProduce(produceList []string) Produce { return produce } -func StageCustomTraceCfg(produce []string, db kv.TemporalRwDB, dirs datadir.Dirs, br services.FullBlockReader, cc *chain.Config, engine consensus.Engine, genesis *types.Genesis, syncCfg *ethconfig.Sync) CustomTraceCfg { - execArgs := &exec4.ExecArgs{ +func StageCustomTraceCfg(produce []string, db kv.TemporalRwDB, dirs datadir.Dirs, br services.FullBlockReader, cc *chain.Config, engine consensus.Engine, genesis *types.Genesis, syncCfg ethconfig.Sync) CustomTraceCfg { + execArgs := &exec3.ExecArgs{ ChainDB: db, BlockReader: br, ChainConfig: cc, @@ -105,7 +106,7 @@ func StageCustomTraceCfg(produce []string, db kv.TemporalRwDB, dirs datadir.Dirs func SpawnCustomTrace(cfg CustomTraceCfg, ctx context.Context, logger log.Logger) error { log.Info("[stage_custom_trace] start params", "produce", cfg.Produce) - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.ExecArgs.BlockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, cfg.ExecArgs.BlockReader)) // 1. Require stage_exec > 0: means don't need handle "half-block execution case here" // 2. Require stage_exec > 0: means has enough state-history @@ -120,31 +121,9 @@ func SpawnCustomTrace(cfg CustomTraceCfg, ctx context.Context, logger log.Logger return errors.New("stage_exec progress is 0. please run `integration stage_exec --batchSize=1m` for couple minutes") } - ac := tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx) - - //TODO: need better way to detect start point. What if domain/index is sparse (has rare events). - txNum := uint64(math.MaxUint64) - if cfg.Produce.ReceiptDomain { - txNum = min(txNum, ac.HistoryProgress(kv.ReceiptDomain, tx)) - } - if cfg.Produce.RCacheDomain { - txNum = min(txNum, ac.HistoryProgress(kv.RCacheDomain, tx)) - } - if cfg.Produce.LogAddr { - txNum = min(txNum, ac.ProgressII(kv.LogAddrIdx, tx)) - } - if cfg.Produce.LogTopic { - txNum = min(txNum, ac.ProgressII(kv.LogTopicIdx, tx)) - } - if cfg.Produce.TraceFrom { - txNum = min(txNum, ac.ProgressII(kv.TracesFromIdx, tx)) - } - if cfg.Produce.TraceTo { - txNum = min(txNum, ac.ProgressII(kv.TracesToIdx, tx)) - } - fromTxNum := txNum + fromTxNum := progressOfDomains(tx, cfg.Produce) var ok bool - ok, startBlock, err = txNumsReader.FindBlockNum(tx, fromTxNum) + startBlock, ok, err = txNumsReader.FindBlockNum(tx, fromTxNum) if err != nil { return fmt.Errorf("getting last executed block: %w", err) } @@ -157,20 +136,26 @@ func SpawnCustomTrace(cfg CustomTraceCfg, ctx context.Context, logger log.Logger } endBlock = execProgress - log.Info("SpawnCustomTrace", "startBlock", startBlock, "endBlock", endBlock) - - var producingDomain kv.Domain - if cfg.Produce.ReceiptDomain { - producingDomain = kv.ReceiptDomain - } - if cfg.Produce.RCacheDomain { - producingDomain = kv.RCacheDomain - } + defer cfg.ExecArgs.BlockReader.Snapshots().(*freezeblocks.RoSnapshots).MadvNormal().DisableReadAhead() + //defer tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx).MadvNormal().DisableReadAhead() - batchSize := uint64(25_000) + log.Info("SpawnCustomTrace", "startBlock", startBlock, "endBlock", endBlock) + batchSize := uint64(50_000) for ; startBlock < endBlock; startBlock += batchSize { - to := min(endBlock+1, startBlock+batchSize) - if err := customTraceBatchProduce(ctx, cfg.Produce, cfg.ExecArgs, cfg.db, startBlock, to, "custom_trace", producingDomain, logger); err != nil { + _nextBlock := startBlock + batchSize + fromStep, toStep, err := exec3.BlkRangeToStepsOnDB(cfg.db, startBlock, _nextBlock, txNumsReader) + if err != nil { + return err + } + if toStep-fromStep > 1 { // reduce big jump + _nextBlock -= batchSize / 2 + } + if toStep-fromStep < 1 { // increase small jump + _nextBlock += batchSize + } + + to := min(endBlock+1, _nextBlock) + if err := customTraceBatchProduce(ctx, cfg.Produce, cfg.ExecArgs, cfg.db, startBlock, to, "custom_trace", logger); err != nil { return err } } @@ -191,33 +176,28 @@ Loop: var m runtime.MemStats dbg.ReadMemStats(&m) //TODO: log progress and list of domains/files - logger.Info("[snapshots] Building files", "alloc", libcommon.ByteCount(m.Alloc), "sys", libcommon.ByteCount(m.Sys)) + logger.Info("[snapshots] Building files", "alloc", common.ByteCount(m.Alloc), "sys", common.ByteCount(m.Sys)) } } log.Info("SpawnCustomTrace finish") - if err := cfg.db.View(ctx, func(tx kv.Tx) error { - ac := tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx) - receiptProgress := ac.HistoryProgress(producingDomain, tx) - accProgress := ac.HistoryProgress(kv.AccountsDomain, tx) - if accProgress != receiptProgress { - _, e1, _ := txNumsReader.FindBlockNum(tx, receiptProgress) - _, e2, _ := txNumsReader.FindBlockNum(tx, accProgress) - - err := fmt.Errorf("[integrity] %s=%d (%d) is behind AccountDomain=%d(%d)", producingDomain.String(), receiptProgress, e1, accProgress, e2) - log.Warn(err.Error()) - return nil + if cfg.Produce.ReceiptDomain { + if err := AssertNotBehindAccounts(cfg.db, kv.ReceiptDomain, txNumsReader); err != nil { + return err + } + } + if cfg.Produce.RCacheDomain { + if err := AssertNotBehindAccounts(cfg.db, kv.RCacheDomain, txNumsReader); err != nil { + return err } - return nil - }); err != nil { - return err } + return nil } -func customTraceBatchProduce(ctx context.Context, produce Produce, cfg *exec4.ExecArgs, db kv.TemporalRwDB, fromBlock, toBlock uint64, logPrefix string, producingDomain kv.Domain, logger log.Logger) error { +func customTraceBatchProduce(ctx context.Context, produce Produce, cfg *exec3.ExecArgs, db kv.TemporalRwDB, fromBlock, toBlock uint64, logPrefix string, logger log.Logger) error { if err := db.Update(ctx, func(tx kv.RwTx) error { - ac := tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx) + ac := state2.AggTx(tx) if err := ac.GreedyPruneHistory(ctx, kv.CommitmentDomain, tx); err != nil { return err } @@ -271,8 +251,7 @@ func customTraceBatchProduce(ctx context.Context, produce Produce, cfg *exec4.Ex toStep = lastTxNum / agg.StepSize() } if err := db.View(ctx, func(tx kv.Tx) error { - ac := tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx) - fromStep = ac.DbgDomain(producingDomain).FirstStepNotInFiles() + fromStep = firstStepNotInFiles(tx, produce) return nil }); err != nil { return err @@ -280,107 +259,66 @@ func customTraceBatchProduce(ctx context.Context, produce Produce, cfg *exec4.Ex if err := agg.BuildFiles2(ctx, fromStep, toStep); err != nil { return err } - for i := 0; i < 2; i++ { // double prune - because background BuildFiles may finish while we did 1-st prune - if err := db.Update(ctx, func(tx kv.RwTx) error { - ac := tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx) - if err := ac.GreedyPruneHistory(ctx, kv.CommitmentDomain, tx); err != nil { - return err - } - if _, err := ac.PruneSmallBatches(ctx, 10*time.Hour, tx); err != nil { // prune part of retired data, before commit - return err - } - return nil - }); err != nil { + if err := db.Update(ctx, func(tx kv.RwTx) error { + ac := tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx) + if err := ac.GreedyPruneHistory(ctx, kv.CommitmentDomain, tx); err != nil { + return err + } + if _, err := ac.PruneSmallBatches(ctx, 10*time.Hour, tx); err != nil { // prune part of retired data, before commit return err } + return nil + }); err != nil { + return err } return nil } -func AssertReceipts(ctx context.Context, cfg *exec4.ExecArgs, tx kv.TemporalRwTx, fromBlock, toBlock uint64) (err error) { - if !assert.Enable { - return - } - if cfg.ChainConfig.Bor != nil { //TODO: enable me - return nil - } - logEvery := time.NewTicker(10 * time.Second) - defer logEvery.Stop() - - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.BlockReader)) - fromTxNum, err := txNumsReader.Min(tx, fromBlock) +func AssertNotBehindAccounts(db kv.RoDB, domain kv.Domain, txNumsReader rawdbv3.TxNumsReader) (err error) { + tx, err := db.BeginRo(context.Background()) if err != nil { return err } - if fromTxNum < 2 { - fromTxNum = 2 //i don't remember why need this - } + defer tx.Rollback() - if toBlock > 0 { - toBlock-- // [fromBlock,toBlock) - } - toTxNum, err := txNumsReader.Max(tx, toBlock) - if err != nil { - return err - } - prevCumGasUsed := -1 - prevBN := uint64(1) - for txNum := fromTxNum; txNum <= toTxNum; txNum++ { - cumGasUsed, _, _, err := rawtemporaldb.ReceiptAsOf(tx, txNum) - if err != nil { - return err - } - blockNum := badFoundBlockNum(tx, prevBN-1, txNumsReader, txNum) - //fmt.Printf("[dbg.integrity] cumGasUsed=%d, txNum=%d, blockNum=%d, prevCumGasUsed=%d\n", cumGasUsed, txNum, blockNum, prevCumGasUsed) - if int(cumGasUsed) == prevCumGasUsed && cumGasUsed != 0 && blockNum == prevBN { - _min, _ := txNumsReader.Min(tx, blockNum) - _max, _ := txNumsReader.Max(tx, blockNum) - err := fmt.Errorf("bad receipt at txnum: %d, block: %d(%d-%d), cumGasUsed=%d, prevCumGasUsed=%d", txNum, blockNum, _min, _max, cumGasUsed, prevCumGasUsed) - log.Warn(err.Error()) - return err - //panic(err) - } - prevCumGasUsed = int(cumGasUsed) - prevBN = blockNum + ac := state2.AggTx(tx) + receiptProgress := ac.HistoryProgress(domain, tx) + accProgress := ac.HistoryProgress(kv.AccountsDomain, tx) + if accProgress != receiptProgress { + e1, _, _ := txNumsReader.FindBlockNum(tx, receiptProgress) + e2, _, _ := txNumsReader.FindBlockNum(tx, accProgress) - select { - case <-ctx.Done(): - return ctx.Err() - case <-logEvery.C: - log.Info("[integrity] ReceiptsNoDuplicates", "progress", fmt.Sprintf("%dk/%dk", txNum/1_000, toTxNum/1_000)) - default: - } + err := fmt.Errorf("[integrity] %s=%d (%d) is behind AccountDomain=%d(%d)", domain.String(), receiptProgress, e1, accProgress, e2) + log.Warn(err.Error()) + return nil } return nil } -func badFoundBlockNum(tx kv.Tx, fromBlock uint64, txNumsReader rawdbv3.TxNumsReader, curTxNum uint64) uint64 { - txNumMax, _ := txNumsReader.Max(tx, fromBlock) - i := uint64(0) - for txNumMax < curTxNum { - i++ - txNumMax, _ = txNumsReader.Max(tx, fromBlock+i) +func AssertReceipts(ctx context.Context, cfg *exec3.ExecArgs, tx kv.TemporalTx, fromBlock, toBlock uint64) (err error) { + if !dbg.AssertEnabled { + return + } + if cfg.ChainConfig.Bor != nil { //TODO: enable me + return nil } - return fromBlock + i + return integrity.ReceiptsNoDuplicatesRange(ctx, fromBlock, toBlock, tx, cfg.BlockReader, true) } -func customTraceBatch(ctx context.Context, produce Produce, cfg *exec4.ExecArgs, tx kv.TemporalRwTx, doms *state2.SharedDomains, fromBlock, toBlock uint64, logPrefix string, logger log.Logger) error { - defer cfg.BlockReader.Snapshots().(*freezeblocks.RoSnapshots).MadvNormal().DisableReadAhead() - //defer tx.(state2.HasAggTx).AggTx().(*state2.AggregatorRoTx).MadvNormal().DisableReadAhead() - +func customTraceBatch(ctx context.Context, produce Produce, cfg *exec3.ExecArgs, tx kv.TemporalRwTx, doms *state2.SharedDomains, fromBlock, toBlock uint64, logPrefix string, logger log.Logger) error { const logPeriod = 5 * time.Second logEvery := time.NewTicker(logPeriod) defer logEvery.Stop() var cumulativeBlobGasUsedInBlock uint64 - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.BlockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, cfg.BlockReader)) fromTxNum, _ := txNumsReader.Min(tx, fromBlock) prevTxNumLog := fromTxNum var m runtime.MemStats - if err := exec4.CustomTraceMapReduce(fromBlock, toBlock, exec4.TraceConsumer{ + if err := exec3.CustomTraceMapReduce(fromBlock, toBlock, exec3.TraceConsumer{ Reduce: func(txTask *state.TxTask, tx kv.Tx) error { if txTask.Error != nil { return txTask.Error @@ -393,15 +331,12 @@ func customTraceBatch(ctx context.Context, produce Produce, cfg *exec4.ExecArgs, doms.SetTx(tx) doms.SetTxNum(txTask.TxNum) - if cfg.ReceiptDomain { + if produce.ReceiptDomain { + var receipt *types.Receipt if !txTask.Final { - var receipt *types.Receipt if txTask.TxIndex >= 0 { receipt = txTask.BlockReceipts[txTask.TxIndex] } - if err := rawtemporaldb.AppendReceipt(doms, receipt, cumulativeBlobGasUsedInBlock); err != nil { - return err - } } if txTask.Final { // block changed @@ -413,17 +348,18 @@ func customTraceBatch(ctx context.Context, produce Produce, cfg *exec4.ExecArgs, } if len(lastReceipt.Logs) > 0 { firstIndex := lastReceipt.Logs[len(lastReceipt.Logs)-1].Index + 1 - receipt := types.Receipt{ + receipt = &types.Receipt{ CumulativeGasUsed: lastReceipt.CumulativeGasUsed, FirstLogIndexWithinBlock: uint32(firstIndex), } - - if err := rawtemporaldb.AppendReceipt(doms, &receipt, cumulativeBlobGasUsedInBlock); err != nil { - return err - } } } + } + if err := rawtemporaldb.AppendReceipt(doms, receipt, cumulativeBlobGasUsedInBlock); err != nil { + return err + } + if txTask.Final { // block changed cumulativeBlobGasUsedInBlock = 0 } } @@ -482,7 +418,8 @@ func customTraceBatch(ctx context.Context, produce Produce, cfg *exec4.ExecArgs, case <-logEvery.C: if prevTxNumLog > 0 { dbg.ReadMemStats(&m) - log.Info(fmt.Sprintf("[%s] Scanned", logPrefix), "block", txTask.BlockNum, "txs/sec", (txTask.TxNum-prevTxNumLog)/uint64(logPeriod.Seconds()), "alloc", libcommon.ByteCount(m.Alloc), "sys", libcommon.ByteCount(m.Sys)) + txsPerSec := (txTask.TxNum - prevTxNumLog) / uint64(logPeriod.Seconds()) + log.Info(fmt.Sprintf("[%s] Scanned", logPrefix), "block", fmt.Sprintf("%dK", txTask.BlockNum/1_000), "tx/s", fmt.Sprintf("%dK", txsPerSec/1_000), "alloc", common.ByteCount(m.Alloc), "sys", common.ByteCount(m.Sys)) } prevTxNumLog = txTask.TxNum default: @@ -495,3 +432,85 @@ func customTraceBatch(ctx context.Context, produce Produce, cfg *exec4.ExecArgs, return nil } + +func progressOfDomains(tx kv.Tx, produce Produce) uint64 { + //TODO: need better way to detect start point. What if domain/index is sparse (has rare events). + ac := state2.AggTx(tx) + txNum := uint64(math.MaxUint64) + if produce.ReceiptDomain { + txNum = min(txNum, ac.HistoryProgress(kv.ReceiptDomain, tx)) + } + if produce.RCacheDomain { + txNum = min(txNum, ac.HistoryProgress(kv.RCacheDomain, tx)) + } + if produce.LogAddr { + txNum = min(txNum, ac.ProgressII(kv.LogAddrIdx, tx)) + } + if produce.LogTopic { + txNum = min(txNum, ac.ProgressII(kv.LogTopicIdx, tx)) + } + if produce.TraceFrom { + txNum = min(txNum, ac.ProgressII(kv.TracesFromIdx, tx)) + } + if produce.TraceTo { + txNum = min(txNum, ac.ProgressII(kv.TracesToIdx, tx)) + } + return txNum +} + +func firstStepNotInFiles(tx kv.Tx, produce Produce) uint64 { + //TODO: need better way to detect start point. What if domain/index is sparse (has rare events). + ac := state2.AggTx(tx) + fromStep := uint64(math.MaxUint64) + if produce.ReceiptDomain { + fromStep = min(fromStep, ac.DbgDomain(kv.ReceiptDomain).FirstStepNotInFiles()) + } + if produce.RCacheDomain { + fromStep = min(fromStep, ac.DbgDomain(kv.RCacheDomain).FirstStepNotInFiles()) + } + if produce.LogAddr { + fromStep = min(fromStep, ac.DbgII(kv.LogAddrIdx).FirstStepNotInFiles()) + } + if produce.LogTopic { + fromStep = min(fromStep, ac.DbgII(kv.LogTopicIdx).FirstStepNotInFiles()) + } + if produce.TraceFrom { + fromStep = min(fromStep, ac.DbgII(kv.TracesFromIdx).FirstStepNotInFiles()) + } + if produce.TraceTo { + fromStep = min(fromStep, ac.DbgII(kv.TracesToIdx).FirstStepNotInFiles()) + } + return fromStep +} + +func StageCustomTraceReset(ctx context.Context, db kv.TemporalRwDB, produce Produce) error { + tx, err := db.BeginTemporalRw(ctx) + if err != nil { + return err + } + defer tx.Rollback() + + var tables []string + if produce.ReceiptDomain { + tables = append(tables, db.Debug().DomainTables(kv.ReceiptDomain)...) + } + if produce.RCacheDomain { + tables = append(tables, db.Debug().DomainTables(kv.RCacheDomain)...) + } + if produce.LogAddr { + tables = append(tables, db.Debug().InvertedIdxTables(kv.LogAddrIdx)...) + } + if produce.LogTopic { + tables = append(tables, db.Debug().InvertedIdxTables(kv.LogTopicIdx)...) + } + if produce.TraceFrom { + tables = append(tables, db.Debug().InvertedIdxTables(kv.TracesFromIdx)...) + } + if produce.TraceTo { + tables = append(tables, db.Debug().InvertedIdxTables(kv.TracesToIdx)...) + } + if err := backup.ClearTables(ctx, tx, tables...); err != nil { + return err + } + return tx.Commit() +} diff --git a/eth/stagedsync/stage_custom_trace_test.go b/eth/stagedsync/stage_custom_trace_test.go new file mode 100644 index 00000000000..e8c9c827ed0 --- /dev/null +++ b/eth/stagedsync/stage_custom_trace_test.go @@ -0,0 +1,71 @@ +// Copyright 2024 The Erigon Authors +// This file is part of Erigon. +// +// Erigon 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. +// +// Erigon 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 Erigon. If not, see . + +package stagedsync_test + +import ( + "context" + "testing" + + "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/erigontech/erigon-lib/kv" + state2 "github.com/erigontech/erigon-lib/state" + "github.com/erigontech/erigon/cmd/rpcdaemon/rpcdaemontest" + "github.com/erigontech/erigon/eth/stagedsync" +) + +func TestCustomTraceReceiptDomain(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + ctx := context.Background() + + m, _, _ := rpcdaemontest.CreateTestSentry(t) + + stageCfg := stagedsync.StageCustomTraceCfg([]string{"receipt"}, m.DB, m.Dirs, m.BlockReader, m.ChainConfig, m.Engine, m.Cfg().Genesis, m.Cfg().Sync) + err := stagedsync.StageCustomTraceReset(ctx, m.DB, stageCfg.Produce) + require.NoError(err) + + err = stagedsync.SpawnCustomTrace(stageCfg, ctx, m.Log) + require.NoError(err) + + err = m.DB.ViewTemporal(ctx, func(rtx kv.TemporalTx) error { + ac := state2.AggTx(rtx) + progress := ac.HistoryProgress(kv.ReceiptDomain, rtx) + assert.Greater(progress, uint64(0), "Receipt domain progress should be greater than 0") + + //check couple empty blocks + cumGasUsed, _, logIndex, err := rawtemporaldb.ReceiptAsOf(rtx, 3) + require.NoError(err) + assert.Equal(0, int(cumGasUsed)) + assert.Equal(0, int(logIndex)) + + cumGasUsed, _, logIndex, err = rawtemporaldb.ReceiptAsOf(rtx, 4) + require.NoError(err) + assert.Equal(21_000, int(cumGasUsed)) + assert.Equal(0, int(logIndex)) + + cumGasUsed, _, logIndex, err = rawtemporaldb.ReceiptAsOf(rtx, 5) + require.NoError(err) + assert.Equal(0, int(cumGasUsed)) + assert.Equal(0, int(logIndex)) + + return nil + }) + require.NoError(err) +} diff --git a/eth/stagedsync/stage_execute.go b/eth/stagedsync/stage_execute.go index 739b717b0d3..d22590ab6fd 100644 --- a/eth/stagedsync/stage_execute.go +++ b/eth/stagedsync/stage_execute.go @@ -181,7 +181,7 @@ func unwindExec3(u *UnwindState, s *StageState, txc wrap.TxContainer, ctx contex } rs := state.NewStateV3(domains, cfg.syncCfg, cfg.chainConfig.Bor != nil, logger) - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, br)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, br)) // unwind all txs of u.UnwindPoint block. 1 txn in begin/end of block - system txs txNum, err := txNumsReader.Min(txc.Tx, u.UnwindPoint+1) @@ -244,10 +244,6 @@ func stageProgress(tx kv.Tx, db kv.RoDB, stage stages.SyncStage) (prevStageProgr return prevStageProgress, nil } -func BorHeimdallStageProgress(tx kv.Tx, cfg BorHeimdallCfg) (prevStageProgress uint64, err error) { - return stageProgress(tx, cfg.db, stages.BorHeimdall) -} - // ================ Erigon3 End ================ func SpawnExecuteBlocksStage(s *StageState, u Unwinder, txc wrap.TxContainer, toBlock uint64, ctx context.Context, cfg ExecuteBlockCfg, logger log.Logger) (err error) { diff --git a/eth/stagedsync/stage_mining_bor_heimdall.go b/eth/stagedsync/stage_mining_bor_heimdall.go deleted file mode 100644 index 3bcf242a4cd..00000000000 --- a/eth/stagedsync/stage_mining_bor_heimdall.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2024 The Erigon Authors -// This file is part of Erigon. -// -// Erigon 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. -// -// Erigon 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 Erigon. If not, see . - -package stagedsync - -import ( - "context" - "fmt" - - "github.com/erigontech/erigon-lib/log/v3" - - "github.com/erigontech/erigon-lib/kv" - "github.com/erigontech/erigon/core/types" - "github.com/erigontech/erigon/dataflow" - "github.com/erigontech/erigon/eth/stagedsync/stages" - "github.com/erigontech/erigon/polygon/bor/finality/whitelist" -) - -func MiningBorHeimdallForward( - ctx context.Context, - cfg BorHeimdallCfg, - stageStage *StageState, - unwinder Unwinder, - tx kv.RwTx, - logger log.Logger, -) error { - if cfg.borConfig == nil || cfg.heimdallClient == nil { - return nil - } - - logPrefix := stageStage.LogPrefix() - headerStageProgress, err := stages.GetStageProgress(tx, stages.Headers) - if err != nil { - return err - } - - header := cfg.miningState.MiningBlock.Header - headerNum := header.Number.Uint64() - if headerNum <= headerStageProgress { - return fmt.Errorf("attempting to mine %d, which is behind current head: %d", headerNum, headerStageProgress) - } - - // Whitelist service is called to check if the bor chain is on the canonical chain according to milestones - whitelistService := whitelist.GetWhitelistingService() - if whitelistService != nil && !whitelistService.IsValidChain(headerNum, []*types.Header{header}) { - hash := header.Hash() - logger.Debug( - fmt.Sprintf("[%s] Verification failed for mined header", logPrefix), - "hash", hash, - "height", headerNum, - "err", err, - ) - dataflow.HeaderDownloadStates.AddChange(headerNum, dataflow.HeaderInvalidated) - if err := unwinder.UnwindTo(headerNum-1, ForkReset(hash), tx); err != nil { - return err - } - return fmt.Errorf("mining on a wrong fork %d:%x", headerNum, hash) - } - - lastSpanID, err := fetchRequiredHeimdallSpansIfNeeded(ctx, headerNum, tx, cfg, logPrefix, logger) - if err != nil { - return err - } - - lastStateSyncEventID, _, err := cfg.blockReader.LastEventId(ctx, tx) - - if err != nil { - return err - } - - lastStateSyncEventID, records, _, fetchTime, err := fetchRequiredHeimdallStateSyncEventsIfNeeded( - ctx, - header, - tx, - cfg.borConfig, - cfg.blockReader, - cfg.heimdallClient, - cfg.bridgeStore, - cfg.chainConfig.ChainID.String(), - logPrefix, - logger, - lastStateSyncEventID, - 0, - ) - - if err != nil { - return err - } - - if err = stageStage.Update(tx, headerNum); err != nil { - return err - } - - logger.Info( - fmt.Sprintf("[%s] Finished processing", logPrefix), - "progress", headerNum, - "lastSpanID", lastSpanID, - "lastStateSyncEventID", lastStateSyncEventID, - "stateSyncEventTotalRecords", records, - "stateSyncEventFetchTime", fetchTime, - ) - - return nil -} diff --git a/eth/stagedsync/stage_mining_bor_heimdall_test.go b/eth/stagedsync/stage_mining_bor_heimdall_test.go deleted file mode 100644 index 997aed868b9..00000000000 --- a/eth/stagedsync/stage_mining_bor_heimdall_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2024 The Erigon Authors -// This file is part of Erigon. -// -// Erigon 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. -// -// Erigon 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 Erigon. If not, see . - -package stagedsync_test - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/erigontech/erigon-lib/log/v3" - - "github.com/erigontech/erigon/eth/stagedsync/stagedsynctest" - "github.com/erigontech/erigon/eth/stagedsync/stages" - "github.com/erigontech/erigon/polygon/heimdall" -) - -func TestMiningBorHeimdallForwardPersistsSpans(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 6640 - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SetMiningBlockEmptyHeader(ctx, t, uint64(numBlocks)) - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunMiningStageForward(ctx, t, stages.MiningBorHeimdall) - - // asserts - spans, err := testHarness.ReadSpansFromDB(ctx) - require.NoError(t, err) - require.Len(t, spans, 3) - require.Equal(t, heimdall.SpanId(0), spans[0].Id) - require.Equal(t, uint64(0), spans[0].StartBlock) - require.Equal(t, uint64(255), spans[0].EndBlock) - require.Equal(t, heimdall.SpanId(1), spans[1].Id) - require.Equal(t, uint64(256), spans[1].StartBlock) - require.Equal(t, uint64(6655), spans[1].EndBlock) - require.Equal(t, heimdall.SpanId(2), spans[2].Id) - require.Equal(t, uint64(6656), spans[2].StartBlock) - require.Equal(t, uint64(13055), spans[2].EndBlock) -} - -func TestMiningBorHeimdallForwardPersistsStateSyncEvents(t *testing.T) { - t.Parallel() - - ctx := context.Background() - numBlocks := 15 - testHarness := stagedsynctest.InitHarness(ctx, t, stagedsynctest.HarnessCfg{ - ChainConfig: stagedsynctest.BorDevnetChainConfigWithNoBlockSealDelays(), - GenerateChainNumBlocks: numBlocks, - LogLvl: log.LvlError, - }) - // pretend-update previous stage progress - testHarness.SetMiningBlockEmptyHeader(ctx, t, uint64(numBlocks)) - testHarness.SaveStageProgress(ctx, t, stages.Headers, uint64(numBlocks)) - - // run stage under test - testHarness.RunMiningStageForward(ctx, t, stages.MiningBorHeimdall) - - // asserts - // 1 event per sprint expected - events, err := testHarness.ReadStateSyncEventsFromDB(ctx) - require.NoError(t, err) - require.Len(t, events, 1) - - lastEventNumPerBlock, err := testHarness.ReadLastStateSyncEventNumPerBlockFromDB(ctx) - require.NoError(t, err) - require.Len(t, lastEventNumPerBlock, 1) - require.Equal(t, uint64(1), lastEventNumPerBlock[16]) -} diff --git a/eth/stagedsync/stage_mining_create_block.go b/eth/stagedsync/stage_mining_create_block.go index 8da7c5bed4a..d766c004677 100644 --- a/eth/stagedsync/stage_mining_create_block.go +++ b/eth/stagedsync/stage_mining_create_block.go @@ -185,7 +185,7 @@ func SpawnMiningCreateBlockStage(s *StageState, txc wrap.TxContainer, cfg Mining uncles: mapset.NewSet[libcommon.Hash](), } - header := core.MakeEmptyHeader(parent, &cfg.chainConfig, timestamp, &cfg.miner.MiningConfig.GasLimit) + header := core.MakeEmptyHeader(parent, &cfg.chainConfig, timestamp, cfg.miner.MiningConfig.GasLimit) if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { logger.Warn("Failed to verify gas limit given by the validator, defaulting to parent gas limit", "err", err) header.GasLimit = parent.GasLimit diff --git a/eth/stagedsync/stage_mining_exec.go b/eth/stagedsync/stage_mining_exec.go index afad10aadcd..25c84a3595a 100644 --- a/eth/stagedsync/stage_mining_exec.go +++ b/eth/stagedsync/stage_mining_exec.go @@ -341,7 +341,7 @@ func filterBadTransactions(transactions []types.Transaction, chainID *uint256.In // Make sure the sender is an EOA (EIP-3607) if !account.IsEmptyCodeHash() { isEoaCodeAllowed := false - if config.IsPrague(header.Time) { + if config.IsPrague(header.Time) || config.IsBhilai(header.Number.Uint64()) { code, err := simStateReader.ReadAccountCode(sender, account.Incarnation) if err != nil { return nil, err diff --git a/eth/stagedsync/stage_polygon_sync.go b/eth/stagedsync/stage_polygon_sync.go index 5c77634382d..15b2a59f8cb 100644 --- a/eth/stagedsync/stage_polygon_sync.go +++ b/eth/stagedsync/stage_polygon_sync.go @@ -73,6 +73,7 @@ func NewPolygonSyncStageCfg( userUnwindTypeOverrides []string, notifications *shards.Notifications, engineAPISwitcher sync.EngineAPISwitcher, + minedBlockReg sync.MinedBlockObserverRegistrar, ) PolygonSyncStageCfg { // using a buffered channel to preserve order of tx actions, // do not expect to ever have more than 50 goroutines blocking on this channel @@ -139,7 +140,7 @@ func NewPolygonSyncStageCfg( syncStore, blockLimit, ) - events := polygonsync.NewTipEvents(logger, p2pService, heimdallService) + events := polygonsync.NewTipEvents(logger, p2pService, heimdallService, minedBlockReg) sync := polygonsync.NewSync( config, logger, @@ -1120,6 +1121,14 @@ func (s polygonSyncStageBridgeStore) Events(context.Context, uint64, uint64) ([] panic("polygonSyncStageBridgeStore.Events not supported") } +func (s polygonSyncStageBridgeStore) EventsByTimeframe(ctx context.Context, timeFrom, timeTo uint64) ([][]byte, error) { + // used for accessing events in execution + // astrid stage integration intends to use the bridge only for scrapping + // not for reading which remains the same in execution (via BlockReader) + // astrid standalone mode introduces its own reader + panic("polygonSyncStageBridgeStore.EventsByTimeframe not supported") +} + func (s polygonSyncStageBridgeStore) BlockEventIdsRange(context.Context, libcommon.Hash, uint64) (uint64, uint64, bool, error) { // used for accessing events in execution // astrid stage integration intends to use the bridge only for scrapping diff --git a/eth/stagedsync/stage_snapshots.go b/eth/stagedsync/stage_snapshots.go index d39c926f336..6c00a6b32fd 100644 --- a/eth/stagedsync/stage_snapshots.go +++ b/eth/stagedsync/stage_snapshots.go @@ -285,7 +285,7 @@ func DownloadAndIndexSnapshotsIfNeed(s *StageState, ctx context.Context, tx kv.R return err } - if err := cfg.blockReader.Snapshots().OpenSegments([]snaptype.Type{coresnaptype.Headers, coresnaptype.Bodies}, true); err != nil { + if err := cfg.blockReader.Snapshots().OpenSegments([]snaptype.Type{coresnaptype.Headers, coresnaptype.Bodies}, true, false); err != nil { return err } @@ -341,10 +341,10 @@ func DownloadAndIndexSnapshotsIfNeed(s *StageState, ctx context.Context, tx kv.R } cfg.blockReader.Snapshots().LogStat("download") - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, cfg.blockReader)) if temporal, ok := tx.(*temporal.Tx); ok { stats.LogStats(temporal, logger, func(endTxNumMinimax uint64) (uint64, error) { - _, histBlockNumProgress, err := txNumsReader.FindBlockNum(tx, endTxNumMinimax) + histBlockNumProgress, _, err := txNumsReader.FindBlockNum(tx, endTxNumMinimax) return histBlockNumProgress, err }) } diff --git a/eth/stagedsync/stagebuilder.go b/eth/stagedsync/stagebuilder.go index cbde166e0ea..76a43ac27be 100644 --- a/eth/stagedsync/stagebuilder.go +++ b/eth/stagedsync/stagebuilder.go @@ -38,11 +38,11 @@ type ChainEventNotifier interface { func MiningStages( ctx context.Context, createBlockCfg MiningCreateBlockCfg, - borHeimdallCfg BorHeimdallCfg, executeBlockCfg ExecuteBlockCfg, sendersCfg SendersCfg, execCfg MiningExecCfg, finish MiningFinishCfg, + astridEnabled bool, ) []*Stage { return []*Stage{ { @@ -56,22 +56,6 @@ func MiningStages( }, Prune: func(u *PruneState, tx kv.RwTx, logger log.Logger) error { return nil }, }, - { - ID: stages.MiningBorHeimdall, - Description: "Download Bor-specific data from Heimdall", - Forward: func(badBlockUnwind bool, s *StageState, u Unwinder, txc wrap.TxContainer, logger log.Logger) error { - if badBlockUnwind { - return nil - } - return MiningBorHeimdallForward(ctx, borHeimdallCfg, s, u, txc.Tx, logger) - }, - Unwind: func(u *UnwindState, s *StageState, txc wrap.TxContainer, logger log.Logger) error { - return BorHeimdallUnwind(u, ctx, s, txc.Tx, borHeimdallCfg) - }, - Prune: func(p *PruneState, tx kv.RwTx, logger log.Logger) error { - return nil - }, - }, { ID: stages.MiningExecution, Description: "Mining: execute new block from txn pool", diff --git a/eth/stagedsync/stagedsynctest/harness.go b/eth/stagedsync/stagedsynctest/harness.go index 69865589fe6..41e93238d80 100644 --- a/eth/stagedsync/stagedsynctest/harness.go +++ b/eth/stagedsync/stagedsynctest/harness.go @@ -50,7 +50,6 @@ import ( "github.com/erigontech/erigon/polygon/bor" "github.com/erigontech/erigon/polygon/bor/borcfg" "github.com/erigontech/erigon/polygon/bor/valset" - "github.com/erigontech/erigon/polygon/bridge" "github.com/erigontech/erigon/polygon/heimdall" "github.com/erigontech/erigon/turbo/services" "github.com/erigontech/erigon/turbo/stages/mock" @@ -67,26 +66,8 @@ func InitHarness(ctx context.Context, t *testing.T, cfg HarnessCfg) Harness { ctrl := gomock.NewController(t) heimdallClient := heimdall.NewMockClient(ctrl) miningState := stagedsync.NewMiningState(ðconfig.Defaults.Miner) - bridgeStore := bridge.NewDbStore(m.DB) - heimdallStore := heimdall.NewDbStore(m.DB) - - bhCfg := stagedsync.StageBorHeimdallCfg( - chainDataDB, - borConsensusDB, - miningState, - *cfg.ChainConfig, - heimdallClient, - heimdallStore, - bridgeStore, - blockReader, - nil, // headerDownloader - nil, // penalize - nil, // recent bor snapshots cached - nil, // signatures - false, - nil, - ) - stateSyncStages := stagedsync.DefaultStages(ctx, stagedsync.SnapshotsCfg{}, stagedsync.HeadersCfg{}, bhCfg, stagedsync.BlockHashesCfg{}, stagedsync.BodiesCfg{}, stagedsync.SendersCfg{}, stagedsync.ExecuteBlockCfg{}, stagedsync.TxLookupCfg{}, stagedsync.FinishCfg{}, true) + + stateSyncStages := stagedsync.DefaultStages(ctx, stagedsync.SnapshotsCfg{}, stagedsync.HeadersCfg{}, stagedsync.BlockHashesCfg{}, stagedsync.BodiesCfg{}, stagedsync.SendersCfg{}, stagedsync.ExecuteBlockCfg{}, stagedsync.TxLookupCfg{}, stagedsync.FinishCfg{}, true) stateSync := stagedsync.New( ethconfig.Defaults.Sync, stateSyncStages, @@ -98,11 +79,11 @@ func InitHarness(ctx context.Context, t *testing.T, cfg HarnessCfg) Harness { miningSyncStages := stagedsync.MiningStages( ctx, stagedsync.MiningCreateBlockCfg{}, - bhCfg, stagedsync.ExecuteBlockCfg{}, stagedsync.SendersCfg{}, stagedsync.MiningExecCfg{}, stagedsync.MiningFinishCfg{}, + false, ) miningSync := stagedsync.New( ethconfig.Defaults.Sync, @@ -127,7 +108,6 @@ func InitHarness(ctx context.Context, t *testing.T, cfg HarnessCfg) Harness { miningSyncStages: miningSyncStages, miningSync: miningSync, miningState: miningState, - bhCfg: bhCfg, heimdallClient: heimdallClient, heimdallProducersOverride: cfg.GetOrCreateDefaultHeimdallProducersOverride(), sealedHeaders: make(map[uint64]*types.Header), @@ -182,7 +162,6 @@ type Harness struct { miningSyncStages []*stagedsync.Stage miningSync *stagedsync.Sync miningState stagedsync.MiningState - bhCfg stagedsync.BorHeimdallCfg heimdallClient *heimdall.MockClient heimdallNextMockSpan *heimdall.Span heimdallLastEventID uint64 @@ -289,8 +268,7 @@ func (h *Harness) SetMiningBlockEmptyHeader(ctx context.Context, t *testing.T, p require.NotNil(t, parent) timestamp := uint64(time.Now().Unix()) - gasLimit := &h.miningState.MiningConfig.GasLimit - h.miningState.MiningBlock.Header = core.MakeEmptyHeader(parent, h.chainConfig, timestamp, gasLimit) + h.miningState.MiningBlock.Header = core.MakeEmptyHeader(parent, h.chainConfig, timestamp, h.miningState.MiningConfig.GasLimit) } func (h *Harness) ReadSpansFromDB(ctx context.Context) (spans []*heimdall.Span, err error) { @@ -666,13 +644,6 @@ func (h *Harness) mockHeimdallClient() { return []*heimdall.EventRecordWithTime{&newEvent}, nil }). AnyTimes() - h.heimdallClient. - EXPECT(). - FetchStateSyncEvent(gomock.Any(), gomock.Any()). - DoAndReturn(func(_ context.Context, _ uint64) (*heimdall.EventRecordWithTime, error) { - return nil, heimdall.ErrEventRecordNotFound - }). - AnyTimes() } func (h *Harness) runSyncStageForwardWithErrorIs( diff --git a/eth/stagedsync/stages/stages.go b/eth/stagedsync/stages/stages.go index 851aada1f1c..495b3ddefc4 100644 --- a/eth/stagedsync/stages/stages.go +++ b/eth/stagedsync/stages/stages.go @@ -34,7 +34,6 @@ type SyncStage string var ( Snapshots SyncStage = "OtterSync" // Snapshots Headers SyncStage = "Headers" // Headers are downloaded, their Proof-Of-Work validity and chaining is verified - BorHeimdall SyncStage = "BorHeimdall" // Downloading data from heimdall corresponding to the downloaded headers (validator sets and sync events) PolygonSync SyncStage = "PolygonSync" // Use polygon sync component to sync headers, bodies and heimdall data CumulativeIndex SyncStage = "CumulativeIndex" // Calculate how much gas has been used up to each block. BlockHashes SyncStage = "BlockHashes" // Headers Number are written, fills blockHash => number bucket @@ -62,7 +61,6 @@ var ( var AllStages = []SyncStage{ Snapshots, Headers, - BorHeimdall, PolygonSync, BlockHashes, Bodies, diff --git a/execution/exec3/historical_trace_worker.go b/execution/exec3/historical_trace_worker.go index c66f1151369..9d63cc301a2 100644 --- a/execution/exec3/historical_trace_worker.go +++ b/execution/exec3/historical_trace_worker.go @@ -34,6 +34,7 @@ import ( "github.com/erigontech/erigon-lib/kv" "github.com/erigontech/erigon-lib/kv/rawdbv3" "github.com/erigontech/erigon-lib/log/v3" + libstate "github.com/erigontech/erigon-lib/state" "github.com/erigontech/erigon/consensus" "github.com/erigontech/erigon/core" "github.com/erigontech/erigon/core/state" @@ -101,18 +102,16 @@ func NewHistoricalTraceWorker( ctx: ctx, execArgs: execArgs, - stateReader: state.NewHistoryReaderV3(), - stateWriter: state.NewNoopWriter(), background: background, + stateReader: state.NewHistoryReaderV3(), - evm: vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, execArgs.ChainConfig, vm.Config{}), callTracer: NewCallTracer(), taskGasPool: new(core.GasPool), vmCfg: &vm.Config{}, } + ie.evm = vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, nil, execArgs.ChainConfig, *ie.vmCfg) ie.taskGasPool.AddBlobGas(execArgs.ChainConfig.GetMaxBlobGasPerBlock(0)) ie.ibs = state.New(ie.stateReader) - return ie } @@ -122,7 +121,6 @@ func (rw *HistoricalTraceWorker) Run() (err error) { defer func() { // convert panic to err - because it's background workers if rec := recover(); rec != nil { err = fmt.Errorf("HistoricalTraceWorker panic: %s, %s", rec, dbg.Stack()) - log.Warn("[HistoricalTraceWorker]", "err", err) } }() defer rw.LogStats() @@ -320,9 +318,6 @@ type ExecArgs struct { Dirs datadir.Dirs ChainConfig *chain.Config Workers int - - ReceiptDomain bool - ReGenRCacheDomain bool } func NewHistoricalTraceWorkers(consumer TraceConsumer, cfg *ExecArgs, ctx context.Context, toTxNum uint64, in *state.QueueWithRetry, workerCount int, outputTxNum *atomic.Uint64, logger log.Logger) *errgroup.Group { @@ -337,7 +332,6 @@ func NewHistoricalTraceWorkers(consumer TraceConsumer, cfg *ExecArgs, ctx contex defer func() { if rec := recover(); rec != nil { err = fmt.Errorf("'reduce worker' paniced: %s, %s", rec, dbg.Stack()) - log.Warn("[HistoricalTraceWorker]", "err", err) } }() defer rws.Close() @@ -347,7 +341,6 @@ func NewHistoricalTraceWorkers(consumer TraceConsumer, cfg *ExecArgs, ctx contex defer func() { if rec := recover(); rec != nil { err = fmt.Errorf("'reduce worker' paniced: %s, %s", rec, dbg.Stack()) - log.Warn("[StageCustomTrace]", "err", err) } }() return doHistoryReduce(consumer, cfg, ctx, toTxNum, outputTxNum, rws, logger) @@ -361,6 +354,7 @@ func doHistoryReduce(consumer TraceConsumer, cfg *ExecArgs, ctx context.Context, return err } defer tx.Rollback() + applyWorker := NewHistoricalTraceWorker(consumer, nil, nil, false, ctx, cfg, logger) defer applyWorker.LogStats() applyWorker.ResetTx(tx) @@ -423,7 +417,6 @@ func processResultQueueHistorical(consumer TraceConsumer, rws *state.ResultsQueu } txTask.CreateReceipt(tx) - if err := consumer.Reduce(txTask, tx); err != nil { return outputTxNum, false, err } @@ -445,11 +438,8 @@ func CustomTraceMapReduce(fromBlock, toBlock uint64, consumer TraceConsumer, ctx br := cfg.BlockReader chainConfig := cfg.ChainConfig - //if chainConfig.Aura != nil && cfg.Workers > 1 { - // panic("gnosis consensus doesn't support parallel exec yet: https://github.com/erigontech/erigon/issues/12054") - //} - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, cfg.BlockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, cfg.BlockReader)) fromTxNum, err := txNumsReader.Min(tx, fromBlock) if err != nil { @@ -473,7 +463,14 @@ func CustomTraceMapReduce(fromBlock, toBlock uint64, consumer TraceConsumer, ctx WorkerCount = cfg.Workers } - log.Info("[custom_trace] batch start", "fromBlock", fromBlock, "toBlock", toBlock, "workers", cfg.Workers, "toTxNum", toTxNum) + { + fromStep, toStep, err := BlkRangeToSteps(tx, fromBlock, toBlock, txNumsReader) + if err != nil { + return err + } + log.Info("[custom_trace] batch start", "blocks", fmt.Sprintf("%dk-%dk", fromBlock/1_000, toBlock/1_000), "steps", fmt.Sprintf("%.2f-%.2f", fromStep, toStep), "workers", cfg.Workers) + } + getHeaderFunc := func(hash common.Hash, number uint64) (h *types.Header) { if tx != nil && WorkerCount == 1 { h, _ = cfg.BlockReader.Header(ctx, tx, hash, number) @@ -569,6 +566,7 @@ func CustomTraceMapReduce(fromBlock, toBlock uint64, consumer TraceConsumer, ctx HistoryExecution: true, BlockReceipts: blockReceipts, } + if txIndex >= 0 && txIndex < len(txs) { txTask.Tx = txs[txIndex] txTask.TxAsMessage, err = txTask.Tx.AsMessage(signer, header.BaseFee, txTask.Rules) @@ -624,8 +622,27 @@ func blockWithSenders(ctx context.Context, db kv.RoDB, tx kv.Tx, blockReader ser if b == nil { return nil, nil } - for _, txn := range b.Transactions() { - _ = txn.Hash() - } return b, err } +func BlkRangeToSteps(tx kv.Tx, fromBlock, toBlock uint64, txNumsReader rawdbv3.TxNumsReader) (float64, float64, error) { + fromTxNum, err := txNumsReader.Min(tx, fromBlock) + if err != nil { + return 0, 0, err + } + toTxNum, err := txNumsReader.Min(tx, toBlock) + if err != nil { + return 0, 0, err + } + + stepSize := libstate.AggTx(tx).StepSize() + return float64(fromTxNum) / float64(stepSize), float64(toTxNum) / float64(stepSize), nil +} + +func BlkRangeToStepsOnDB(db kv.RoDB, fromBlock, toBlock uint64, txNumsReader rawdbv3.TxNumsReader) (float64, float64, error) { + tx, err := db.BeginRo(context.Background()) + if err != nil { + return 0, 0, err + } + defer tx.Rollback() + return BlkRangeToSteps(tx, fromBlock, toBlock, txNumsReader) +} diff --git a/p2p/sentry/simulator/sentry_simulator.go b/p2p/sentry/simulator/sentry_simulator.go index 3e271b5badf..1b595f1474d 100644 --- a/p2p/sentry/simulator/sentry_simulator.go +++ b/p2p/sentry/simulator/sentry_simulator.go @@ -408,7 +408,7 @@ func (s *server) getHeader(ctx context.Context, blockNum uint64) (*coretypes.Hea } } - s.activeSnapshots.OpenSegments([]snaptype.Type{coresnaptype.Headers}, true) + s.activeSnapshots.OpenSegments([]snaptype.Type{coresnaptype.Headers}, true, true) header, err = s.blockReader.Header(ctx, nil, common.Hash{}, blockNum) diff --git a/params/chainspecs/amoy.json b/params/chainspecs/amoy.json index 7c15a7cf276..501035313ec 100644 --- a/params/chainspecs/amoy.json +++ b/params/chainspecs/amoy.json @@ -41,6 +41,7 @@ "agraBlock": 73100, "napoliBlock": 5423600, "ahmedabadBlock": 11865856, + "bhilaiBlock": 22765056, "blockAlloc": { "11865856": { "0000000000000000000000000000000000001001": { diff --git a/params/mining.go b/params/mining.go index 7993d3be43a..d0c97729c4f 100644 --- a/params/mining.go +++ b/params/mining.go @@ -34,7 +34,7 @@ type MiningConfig struct { SigKey *ecdsa.PrivateKey // ECDSA private key for signing blocks Notify []string `toml:",omitempty"` // HTTP URL list to be notified of new work packages(only useful in ethash). ExtraData hexutility.Bytes `toml:",omitempty"` // Block extra data set by the miner - GasLimit uint64 // Target gas limit for mined blocks. + GasLimit *uint64 // Target gas limit for mined blocks. GasPrice *big.Int // Minimum gas price for mining a transaction Recommit time.Duration // The time interval for miner to re-create mining work. } diff --git a/params/protocol_params.go b/params/protocol_params.go index 4fd8e387b20..41db5845266 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -129,11 +129,12 @@ const ( // Introduced in Tangerine Whistle (Eip 150) CreateBySelfdestructGas uint64 = 25000 - BaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. - BaseFeeChangeDenominatorPostDelhi = 16 // Bounds the amount the base fee can change between blocks post delhi hard fork for polygon networks. - ElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. - InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. - InitialBaseFeeForBSC = 0 // Initial base fee for EIP-1559 blocks on bsc Mainnet + BaseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks. + BaseFeeChangeDenominatorPostDelhi = 16 // Bounds the amount the base fee can change between blocks post delhi hard fork for polygon networks. + BaseFeeChangeDenominatorPostBhilai = 64 // Bounds the amount the base fee can change between blocks post bhilai hard fork for polygon networks. + ElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. + InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. + InitialBaseFeeForBSC = 0 // Initial base fee for EIP-1559 blocks on bsc Mainnet MaxCodeSize = 24576 // Maximum bytecode to permit for a contract MaxCodeSizePostAhmedabad = 32768 // Maximum bytecode to permit for a contract post Ahmedabad hard fork (bor / polygon pos) (32KB) diff --git a/polygon/bor/bor.go b/polygon/bor/bor.go index 3bdaf31543e..443f927949e 100644 --- a/polygon/bor/bor.go +++ b/polygon/bor/bor.go @@ -259,6 +259,7 @@ type spanReader interface { type bridgeReader interface { Events(ctx context.Context, blockHash libcommon.Hash, blockNum uint64) ([]*types.Message, error) + EventsWithinTime(ctx context.Context, timeFrom, timeTo time.Time) ([]*types.Message, error) EventTxnLookup(ctx context.Context, borTxHash libcommon.Hash) (uint64, bool, error) } @@ -270,8 +271,19 @@ func ValidateHeaderTime( config *borcfg.BorConfig, signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address], ) error { - if header.Time > uint64(now.Unix()) { - return consensus.ErrFutureBlock + if config.IsBhilai(header.Number.Uint64()) { + // Don't waste time checking blocks from the future but allow a buffer of block time for + // early block announcements. Note that this is a loose check and would allow early blocks + // from non-primary producer. Such blocks will be rejected later when we know the succession + // number of the signer in the current sprint. + if header.Time > uint64(now.Unix())+config.CalculatePeriod(header.Number.Uint64()) { + return fmt.Errorf("%w: expected: %s(%s), got: %s", consensus.ErrFutureBlock, time.Unix(now.Unix(), 0), now, time.Unix(int64(header.Time), 0)) + } + } else { + // Don't waste time checking blocks from the future + if header.Time > uint64(now.Unix()) { + return fmt.Errorf("%w: expected: %s(%s), got: %s", consensus.ErrFutureBlock, time.Unix(now.Unix(), 0), now, time.Unix(int64(header.Time), 0)) + } } if parent == nil { @@ -288,7 +300,14 @@ func ValidateHeaderTime( return err } - if header.Time < MinNextBlockTime(parent, succession, config) { + // Post Bhilai HF, reject blocks form non-primary producers if they're earlier than the expected time + if config.IsBhilai(header.Number.Uint64()) && succession != 0 { + if header.Time > uint64(now.Unix()) { + return fmt.Errorf("%w: expected: %s(%s), got: %s", consensus.ErrFutureBlock, time.Unix(now.Unix(), 0), now, time.Unix(int64(header.Time), 0)) + } + } + + if parent != nil && header.Time < MinNextBlockTime(parent, succession, config) { return &BlockTooSoonError{header.Number.Uint64(), succession} } @@ -495,11 +514,25 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head if header.Number == nil { return errUnknownBlock } + number := header.Number.Uint64() + now := time.Now().Unix() // Don't waste time checking blocks from the future - if header.Time > uint64(time.Now().Unix()) { - return consensus.ErrFutureBlock + // Allow early blocks if Bhilai HF is enabled + if c.config.IsBhilai(number) { + // Don't waste time checking blocks from the future but allow a buffer of block time for + // early block announcements. Note that this is a loose check and would allow early blocks + // from non-primary producer. Such blocks will be rejected later when we know the succession + // number of the signer in the current sprint. + if header.Time > uint64(now)+c.config.CalculatePeriod(number) { + return fmt.Errorf("%w: expected: %s, got: %s", consensus.ErrFutureBlock, time.Unix(now, 0), time.Unix(int64(header.Time), 0)) + } + } else { + // Don't waste time checking blocks from the future + if header.Time > uint64(now) { + return fmt.Errorf("%w: expected: %s, got: %s", consensus.ErrFutureBlock, time.Unix(now, 0), time.Unix(int64(header.Time), 0)) + } } if err := ValidateHeaderUnusedFields(header); err != nil { @@ -931,10 +964,23 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s // where it fetches producers internally. As we fetch data from span // in Erigon, use directly the `GetCurrentProducers` function. if c.config.IsSprintEnd(number) { - spanID := uint64(heimdall.SpanIdAt(number + 1)) - newValidators, err := c.spanner.GetCurrentProducers(spanID, chain.(ChainHeaderReader)) - if err != nil { - return errUnknownValidators + var newValidators []*valset.Validator + + if c.useSpanReader { + validators, err := c.spanReader.Producers(context.Background(), number+1) + if err != nil { + return err + } + + newValidators = validators.Validators + } else { + var err error + spanID := uint64(heimdall.SpanIdAt(number + 1)) + + newValidators, err = c.spanner.GetCurrentProducers(spanID, chain.(ChainHeaderReader)) + if err != nil { + return errUnknownValidators + } } // sort validator by address @@ -1003,8 +1049,19 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s } header.Time = MinNextBlockTime(parent, succession, c.config) - if header.Time < uint64(time.Now().Unix()) { - header.Time = uint64(time.Now().Unix()) + now := time.Now() + if header.Time < uint64(now.Unix()) { + header.Time = uint64(now.Unix()) + } else { + // For primary validators, wait until the current block production window + // starts. This prevents bor from starting to build next block before time + // as we'd like to wait for new transactions. Although this change doesn't + // need a check for hard fork as it doesn't change any consensus rules, we + // still keep it for safety and testing. + if c.config.IsBhilai(number) && succession == 0 { + startTime := time.Unix(int64(header.Time)-int64(c.config.CalculatePeriod(number)), 0) + time.Sleep(time.Until(startTime)) + } } return nil @@ -1043,7 +1100,7 @@ func (c *Bor) Finalize(config *chain.Config, header *types.Header, state *state. } // commit states - if err := c.CommitStates(state, header, cx, syscall, logger); err != nil { + if err := c.CommitStates(state, header, cx, syscall, logger, false); err != nil { err := fmt.Errorf("Finalize.CommitStates: %w", err) c.logger.Error("[bor] Error while committing states", "err", err) return nil, types.Receipts{}, nil, err @@ -1109,7 +1166,7 @@ func (c *Bor) FinalizeAndAssemble(chainConfig *chain.Config, header *types.Heade return nil, nil, types.Receipts{}, nil, err } // commit states - if err := c.CommitStates(state, header, cx, syscall, logger); err != nil { + if err := c.CommitStates(state, header, cx, syscall, logger, true); err != nil { err := fmt.Errorf("FinalizeAndAssemble.CommitStates: %w", err) c.logger.Error("[bor] committing states", "err", err) return nil, nil, types.Receipts{}, nil, err @@ -1135,6 +1192,9 @@ func (c *Bor) FinalizeAndAssemble(chainConfig *chain.Config, header *types.Heade func (c *Bor) Initialize(config *chain.Config, chain consensus.ChainHeaderReader, header *types.Header, state *state.IntraBlockState, syscall consensus.SysCallCustom, logger log.Logger, tracer *tracing.Hooks) error { + if chain != nil && chain.Config().IsBhilai(header.Number.Uint64()) { + misc.StoreBlockHashesEip2935(header, state) + } return nil } @@ -1152,7 +1212,7 @@ func (c *Bor) Authorize(currentSigner libcommon.Address, signFn SignerFn) { func (c *Bor) Seal(chain consensus.ChainHeaderReader, blockWithReceipts *types.BlockWithReceipts, results chan<- *types.BlockWithReceipts, stop <-chan struct{}) error { block := blockWithReceipts.Block receipts := blockWithReceipts.Receipts - header := block.HeaderNoCopy() + header := block.Header() // Sealing the genesis block is not supported number := header.Number.Uint64() @@ -1193,8 +1253,15 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, blockWithReceipts *types.B } } + var delay time.Duration // Sweet, the protocol permits us to sign the block, wait for our time - delay := time.Until(time.Unix(int64(header.Time), 0)) + if c.config.IsBhilai(header.Number.Uint64()) && successionNumber == 0 { + // For primary producers, set the delay to `header.Time - block time` instead of `header.Time` + // for early block announcement instead of waiting for full block time. + delay = time.Until(time.Unix(int64(header.Time)-int64(c.config.CalculatePeriod(number)), 0)) + } else { + delay = time.Until(time.Unix(int64(header.Time), 0)) // Wait until we reach header time + } // wiggle was already accounted for in header.Time, this is just for logging wiggle := time.Duration(successionNumber) * time.Duration(c.config.CalculateBackupMultiplier(number)) * time.Second @@ -1546,13 +1613,51 @@ func (c *Bor) CommitStates( chain statefull.ChainContext, syscall consensus.SystemCall, logger log.Logger, + fetchEventsWithingTime bool, ) error { blockNum := header.Number.Uint64() if c.useBridgeReader { - events, err := c.bridgeReader.Events(c.execCtx, header.Hash(), blockNum) - if err != nil { - return err + var events []*types.Message + var err error + + if fetchEventsWithingTime { + sprintLength := c.config.CalculateSprintLength(blockNum) + + if blockNum < sprintLength { + return nil + } + + prevSprintStart := chain.Chain.GetHeaderByNumber(blockNum - sprintLength) + stateSyncDelay := c.config.CalculateStateSyncDelay(blockNum) + + timeFrom := time.Unix(int64(prevSprintStart.Time-stateSyncDelay), 0) + timeTo := time.Unix(int64(header.Time-stateSyncDelay), 0) + + // Previous sprint was not indore. + if !c.config.IsIndore(prevSprintStart.Number.Uint64()) { + if prevSprintStart.Number.Uint64() >= sprintLength { + prevPrevSprintStart := chain.Chain.GetHeaderByNumber(prevSprintStart.Number.Uint64() - sprintLength) + timeFrom = time.Unix(int64(prevPrevSprintStart.Time), 0) + } else { + timeFrom = time.Unix(0, 0) + } + } + + // Current sprint was not indore. + if !c.config.IsIndore(blockNum) { + timeTo = time.Unix(int64(prevSprintStart.Time), 0) + } + + events, err = c.bridgeReader.EventsWithinTime(c.execCtx, timeFrom, timeTo) + if err != nil { + return err + } + } else { + events, err = c.bridgeReader.Events(c.execCtx, header.Hash(), blockNum) + if err != nil { + return err + } } for _, event := range events { diff --git a/polygon/bor/bor_internal_test.go b/polygon/bor/bor_internal_test.go index bd0fc55f5fe..ab2069fc5ca 100644 --- a/polygon/bor/bor_internal_test.go +++ b/polygon/bor/bor_internal_test.go @@ -18,13 +18,19 @@ package bor import ( "context" + "math/big" "testing" + "time" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" libcommon "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon/consensus" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/params" + "github.com/erigontech/erigon/polygon/bor/statefull" "github.com/erigontech/erigon/polygon/bor/valset" "github.com/erigontech/erigon/polygon/heimdall" ) @@ -57,6 +63,10 @@ func (m mockBridgeReader) Events(context.Context, libcommon.Hash, uint64) ([]*ty panic("mock") } +func (m mockBridgeReader) EventsWithinTime(context.Context, time.Time, time.Time) ([]*types.Message, error) { + panic("mock") +} + func (m mockBridgeReader) EventTxnLookup(context.Context, libcommon.Hash) (uint64, bool, error) { panic("mock") } @@ -72,3 +82,58 @@ func (m mockSpanReader) Span(context.Context, uint64) (*heimdall.Span, bool, err func (m mockSpanReader) Producers(context.Context, uint64) (*valset.ValidatorSet, error) { panic("mock") } + +func TestCommitStatesIndore(t *testing.T) { + ctrl := gomock.NewController(t) + cr := consensus.NewMockChainReader(ctrl) + br := NewMockbridgeReader(ctrl) + + bor := New(params.BorDevnetChainConfig, nil, nil, nil, nil, nil, nil, br, nil) + + header := &types.Header{ + Number: big.NewInt(112), + Time: 1744000028, + } + + contractAddr := libcommon.HexToAddress("a1") + + cr.EXPECT().GetHeaderByNumber(uint64(96)).Return(&types.Header{ + Number: big.NewInt(96), + Time: 1744000000, + }) + br.EXPECT().EventsWithinTime(gomock.Any(), time.Unix(1744000000-128, 0), time.Unix(1744000028-128, 0)).Return( + []*types.Message{ + types.NewMessage( + libcommon.HexToAddress(""), + &contractAddr, + 0, + uint256.NewInt(0), + 0, + nil, + nil, + nil, + nil, + nil, + false, + false, + nil, + ), + }, nil, + ) + + called := 0 + + syscall := func(contract libcommon.Address, data []byte) ([]byte, error) { + require.Equal(t, contract, contractAddr) + called++ + + return nil, nil + } + + err := bor.CommitStates(nil, header, statefull.ChainContext{ + Chain: cr, + }, syscall, nil, true) + + require.Nil(t, err) + require.Equal(t, called, 1) +} diff --git a/polygon/bor/bor_test.go b/polygon/bor/bor_test.go index 68787bf189d..edfb7f5d1a0 100644 --- a/polygon/bor/bor_test.go +++ b/polygon/bor/bor_test.go @@ -75,7 +75,7 @@ func (h test_heimdall) FetchStateSyncEvents(ctx context.Context, fromID uint64, return nil, nil } -func (h *test_heimdall) FetchStateSyncEvent(ctx context.Context, id uint64) (*heimdall.EventRecordWithTime, error) { +func (h *test_heimdall) FetchChainManagerStatus(ctx context.Context) (*heimdall.ChainManagerStatus, error) { return nil, nil } diff --git a/polygon/bor/borcfg/bor_config.go b/polygon/bor/borcfg/bor_config.go index 1ad3629744b..de06ee42661 100644 --- a/polygon/bor/borcfg/bor_config.go +++ b/polygon/bor/borcfg/bor_config.go @@ -43,6 +43,7 @@ type BorConfig struct { AgraBlock *big.Int `json:"agraBlock"` // Agra switch block (nil = no fork, 0 = already on Agra) NapoliBlock *big.Int `json:"napoliBlock"` // Napoli switch block (nil = no fork, 0 = already on Napoli) AhmedabadBlock *big.Int `json:"ahmedabadBlock"` // Ahmedabad switch block (nil = no fork, 0 = already on Ahmedabad) + BhilaiBlock *big.Int `json:"bhilaiBlock"` // Bhilai switch block (nil = no fork, 0 = already on Ahmedabad) StateSyncConfirmationDelay map[string]uint64 `json:"stateSyncConfirmationDelay"` // StateSync Confirmation Delay, in seconds, to calculate `to` sprints sprints @@ -168,6 +169,14 @@ func (c *BorConfig) GetAhmedabadBlock() *big.Int { return c.AhmedabadBlock } +func (c *BorConfig) IsBhilai(number uint64) bool { + return isForked(c.BhilaiBlock, number) +} + +func (c *BorConfig) GetBhilaiBlock() *big.Int { + return c.BhilaiBlock +} + func (c *BorConfig) CalculateStateSyncDelay(number uint64) uint64 { return chain.ConfigValueLookup(c.StateSyncConfirmationDelay, number) } diff --git a/polygon/bor/bridge_reader_mock.go b/polygon/bor/bridge_reader_mock.go new file mode 100644 index 00000000000..db128802342 --- /dev/null +++ b/polygon/bor/bridge_reader_mock.go @@ -0,0 +1,162 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/erigontech/erigon/polygon/bor (interfaces: bridgeReader) +// +// Generated by this command: +// +// mockgen -typed=true -destination=./bridge_reader_mock.go -package=bor . bridgeReader +// + +// Package bor is a generated GoMock package. +package bor + +import ( + context "context" + reflect "reflect" + time "time" + + common "github.com/erigontech/erigon-lib/common" + types "github.com/erigontech/erigon/core/types" + gomock "go.uber.org/mock/gomock" +) + +// MockbridgeReader is a mock of bridgeReader interface. +type MockbridgeReader struct { + ctrl *gomock.Controller + recorder *MockbridgeReaderMockRecorder + isgomock struct{} +} + +// MockbridgeReaderMockRecorder is the mock recorder for MockbridgeReader. +type MockbridgeReaderMockRecorder struct { + mock *MockbridgeReader +} + +// NewMockbridgeReader creates a new mock instance. +func NewMockbridgeReader(ctrl *gomock.Controller) *MockbridgeReader { + mock := &MockbridgeReader{ctrl: ctrl} + mock.recorder = &MockbridgeReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockbridgeReader) EXPECT() *MockbridgeReaderMockRecorder { + return m.recorder +} + +// EventTxnLookup mocks base method. +func (m *MockbridgeReader) EventTxnLookup(ctx context.Context, borTxHash common.Hash) (uint64, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EventTxnLookup", ctx, borTxHash) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// EventTxnLookup indicates an expected call of EventTxnLookup. +func (mr *MockbridgeReaderMockRecorder) EventTxnLookup(ctx, borTxHash any) *MockbridgeReaderEventTxnLookupCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventTxnLookup", reflect.TypeOf((*MockbridgeReader)(nil).EventTxnLookup), ctx, borTxHash) + return &MockbridgeReaderEventTxnLookupCall{Call: call} +} + +// MockbridgeReaderEventTxnLookupCall wrap *gomock.Call +type MockbridgeReaderEventTxnLookupCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockbridgeReaderEventTxnLookupCall) Return(arg0 uint64, arg1 bool, arg2 error) *MockbridgeReaderEventTxnLookupCall { + c.Call = c.Call.Return(arg0, arg1, arg2) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockbridgeReaderEventTxnLookupCall) Do(f func(context.Context, common.Hash) (uint64, bool, error)) *MockbridgeReaderEventTxnLookupCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockbridgeReaderEventTxnLookupCall) DoAndReturn(f func(context.Context, common.Hash) (uint64, bool, error)) *MockbridgeReaderEventTxnLookupCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Events mocks base method. +func (m *MockbridgeReader) Events(ctx context.Context, blockHash common.Hash, blockNum uint64) ([]*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Events", ctx, blockHash, blockNum) + ret0, _ := ret[0].([]*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Events indicates an expected call of Events. +func (mr *MockbridgeReaderMockRecorder) Events(ctx, blockHash, blockNum any) *MockbridgeReaderEventsCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Events", reflect.TypeOf((*MockbridgeReader)(nil).Events), ctx, blockHash, blockNum) + return &MockbridgeReaderEventsCall{Call: call} +} + +// MockbridgeReaderEventsCall wrap *gomock.Call +type MockbridgeReaderEventsCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockbridgeReaderEventsCall) Return(arg0 []*types.Message, arg1 error) *MockbridgeReaderEventsCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockbridgeReaderEventsCall) Do(f func(context.Context, common.Hash, uint64) ([]*types.Message, error)) *MockbridgeReaderEventsCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockbridgeReaderEventsCall) DoAndReturn(f func(context.Context, common.Hash, uint64) ([]*types.Message, error)) *MockbridgeReaderEventsCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// EventsWithinTime mocks base method. +func (m *MockbridgeReader) EventsWithinTime(ctx context.Context, timeFrom, timeTo time.Time) ([]*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EventsWithinTime", ctx, timeFrom, timeTo) + ret0, _ := ret[0].([]*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EventsWithinTime indicates an expected call of EventsWithinTime. +func (mr *MockbridgeReaderMockRecorder) EventsWithinTime(ctx, timeFrom, timeTo any) *MockbridgeReaderEventsWithinTimeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventsWithinTime", reflect.TypeOf((*MockbridgeReader)(nil).EventsWithinTime), ctx, timeFrom, timeTo) + return &MockbridgeReaderEventsWithinTimeCall{Call: call} +} + +// MockbridgeReaderEventsWithinTimeCall wrap *gomock.Call +type MockbridgeReaderEventsWithinTimeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockbridgeReaderEventsWithinTimeCall) Return(arg0 []*types.Message, arg1 error) *MockbridgeReaderEventsWithinTimeCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockbridgeReaderEventsWithinTimeCall) Do(f func(context.Context, time.Time, time.Time) ([]*types.Message, error)) *MockbridgeReaderEventsWithinTimeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockbridgeReaderEventsWithinTimeCall) DoAndReturn(f func(context.Context, time.Time, time.Time) ([]*types.Message, error)) *MockbridgeReaderEventsWithinTimeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/polygon/bor/spanner.go b/polygon/bor/spanner.go index 78a27552d4d..ff79f0e0f35 100644 --- a/polygon/bor/spanner.go +++ b/polygon/bor/spanner.go @@ -18,6 +18,7 @@ package bor import ( "encoding/hex" + "errors" "math/big" "github.com/erigontech/erigon-lib/log/v3" @@ -128,6 +129,10 @@ func (c *ChainSpanner) GetCurrentProducers(spanId uint64, chain ChainHeaderReade span := chain.BorSpan(spanId) + if span == nil { + return nil, errors.New("no span found") + } + producers := make([]*valset.Validator, len(span.SelectedProducers)) for i := range span.SelectedProducers { producers[i] = &span.SelectedProducers[i] diff --git a/polygon/bridge/mdbx_store.go b/polygon/bridge/mdbx_store.go index 27fbc639c5e..9a338212c53 100644 --- a/polygon/bridge/mdbx_store.go +++ b/polygon/bridge/mdbx_store.go @@ -52,6 +52,7 @@ var databaseTablesCfg = kv.TableCfg{ kv.BorEvents: {}, kv.BorEventNums: {}, kv.BorEventProcessedBlocks: {}, + kv.BorEventTimes: {}, kv.BorTxLookup: {}, } @@ -248,6 +249,16 @@ func (s *MdbxStore) PutEvents(ctx context.Context, events []*heimdall.EventRecor return tx.Commit() } +func (s *MdbxStore) EventsByTimeframe(ctx context.Context, timeFrom, timeTo uint64) ([][]byte, error) { + tx, err := s.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + return txStore{tx}.EventsByTimeframe(ctx, timeFrom, timeTo) +} + // Events gets raw events, start inclusive, end exclusive func (s *MdbxStore) Events(ctx context.Context, start, end uint64) ([][]byte, error) { tx, err := s.db.BeginRo(ctx) @@ -496,9 +507,14 @@ func (s txStore) PutEvents(ctx context.Context, events []*heimdall.EventRecordWi return err } - k := event.MarshallIdBytes() - err = tx.Put(kv.BorEvents, k, v) - if err != nil { + evID := event.MarshallIdBytes() + evTime := event.MarshallTimeBytes() + + if err = tx.Put(kv.BorEvents, evID, v); err != nil { + return err + } + + if err = tx.Put(kv.BorEventTimes, evTime, evID); err != nil { return err } } @@ -506,6 +522,38 @@ func (s txStore) PutEvents(ctx context.Context, events []*heimdall.EventRecordWi return nil } +// returns events withing [timeFrom, timeTo) interval. +func (s txStore) EventsByTimeframe(ctx context.Context, timeFrom, timeTo uint64) ([][]byte, error) { + var events [][]byte + + kStart := make([]byte, 8) + binary.BigEndian.PutUint64(kStart, timeFrom) + + kEnd := make([]byte, 8) + binary.BigEndian.PutUint64(kEnd, timeTo) + + it, err := s.tx.Range(kv.BorEventTimes, kStart, kEnd, order.Asc, kv.Unlim) + if err != nil { + return nil, err + } + + for it.HasNext() { + _, evID, err := it.Next() + if err != nil { + return nil, err + } + + v, err := s.tx.GetOne(kv.BorEvents, evID) + if err != nil { + return nil, err + } + + events = append(events, bytes.Clone(v)) + } + + return events, nil +} + // Events gets raw events, start inclusive, end exclusive func (s txStore) Events(ctx context.Context, start, end uint64) ([][]byte, error) { var events [][]byte @@ -669,7 +717,7 @@ func (s txStore) PruneEvents(ctx context.Context, blocksTo uint64, blocksDeleteL } defer c1.Close() counter := blocksDeleteLimit - for k, _, err = c1.First(); err == nil && k != nil && counter > 0; k, _, err = c1.Next() { + for k, v, err = c1.First(); err == nil && k != nil && counter > 0; k, v, err = c1.Next() { eventId := binary.BigEndian.Uint64(k) if eventId >= eventIdTo { break @@ -677,6 +725,16 @@ func (s txStore) PruneEvents(ctx context.Context, blocksTo uint64, blocksDeleteL if err = c1.DeleteCurrent(); err != nil { return deleted, err } + + var event heimdall.EventRecordWithTime + if err := event.UnmarshallBytes(v); err != nil { + return deleted, err + } + + if err := tx.Delete(kv.BorEventTimes, event.MarshallTimeBytes()); err != nil { + return deleted, err + } + deleted++ counter-- } @@ -766,10 +824,21 @@ func UnwindEvents(tx kv.RwTx, unwindPoint uint64) error { defer eventCursor.Close() var k []byte - for k, _, err = eventCursor.Seek(from); err == nil && k != nil; k, _, err = eventCursor.Next() { + var v []byte + + for k, v, err = eventCursor.Seek(from); err == nil && k != nil; k, v, err = eventCursor.Next() { if err = eventCursor.DeleteCurrent(); err != nil { return err } + + var event heimdall.EventRecordWithTime + if err := event.UnmarshallBytes(v); err != nil { + return err + } + + if err := tx.Delete(kv.BorEventTimes, event.MarshallTimeBytes()); err != nil { + return err + } } return err diff --git a/polygon/bridge/reader.go b/polygon/bridge/reader.go index 2df4afb6528..924dd96affc 100644 --- a/polygon/bridge/reader.go +++ b/polygon/bridge/reader.go @@ -19,6 +19,7 @@ package bridge import ( "context" "fmt" + "time" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" @@ -70,6 +71,34 @@ func (r *Reader) Prepare(ctx context.Context) error { return r.store.Prepare(ctx) } +func (r *Reader) EventsWithinTime(ctx context.Context, timeFrom, timeTo time.Time) ([]*types.Message, error) { + events, err := r.store.EventsByTimeframe(ctx, uint64(timeFrom.Unix()), uint64(timeTo.Unix())) + if err != nil { + return nil, err + } + + eventsRaw := make([]*types.Message, 0, len(events)) + + // convert to message + for _, event := range events { + msg := types.NewMessage( + state.SystemAddress, + &r.stateClientAddress, + 0, u256.Num0, + core.SysCallGasLimit, + u256.Num0, + nil, nil, + event, nil, false, + true, + nil, + ) + + eventsRaw = append(eventsRaw, msg) + } + + return eventsRaw, nil +} + // Events returns all sync events at blockNum func (r *Reader) Events(ctx context.Context, blockHash libcommon.Hash, blockNum uint64) ([]*types.Message, error) { start, end, ok, err := r.store.BlockEventIdsRange(ctx, blockHash, blockNum) diff --git a/polygon/bridge/service.go b/polygon/bridge/service.go index 3c4b33bfc39..19202b6bd0a 100644 --- a/polygon/bridge/service.go +++ b/polygon/bridge/service.go @@ -421,6 +421,10 @@ func (s *Service) Unwind(ctx context.Context, blockNum uint64) error { return nil } +func (s *Service) EventsWithinTime(ctx context.Context, timeFrom, timeTo time.Time) ([]*types.Message, error) { + return s.reader.EventsWithinTime(ctx, timeFrom, timeTo) +} + // Events returns all sync events at blockNum func (s *Service) Events(ctx context.Context, blockHash libcommon.Hash, blockNum uint64) ([]*types.Message, error) { return s.reader.Events(ctx, blockHash, blockNum) diff --git a/polygon/bridge/service_test.go b/polygon/bridge/service_test.go index 5df029068e8..e257c23a4ef 100644 --- a/polygon/bridge/service_test.go +++ b/polygon/bridge/service_test.go @@ -435,3 +435,107 @@ func TestService_ProcessNewBlocksWithZeroOverride(t *testing.T) { cancel() wg.Wait() } + +func TestReaderEventsWithinTime(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + heimdallClient, b := setup(t, defaultBorConfig) + event1 := &heimdall.EventRecordWithTime{ + EventRecord: heimdall.EventRecord{ + ID: 1, + ChainID: "80002", + Data: hexutil.MustDecode("0x01"), + }, + Time: time.Unix(50, 0), + } + event1Data, err := event1.MarshallBytes() + require.NoError(t, err) + event2 := &heimdall.EventRecordWithTime{ + EventRecord: heimdall.EventRecord{ + ID: 2, + ChainID: "80002", + Data: hexutil.MustDecode("0x02"), + }, + Time: time.Unix(99, 0), + } + event2Data, err := event2.MarshallBytes() + require.NoError(t, err) + event3 := &heimdall.EventRecordWithTime{ + EventRecord: heimdall.EventRecord{ + ID: 3, + ChainID: "80002", + Data: hexutil.MustDecode("0x03"), + }, + Time: time.Unix(199, 0), + } + event3Data, err := event3.MarshallBytes() + require.NoError(t, err) + event4 := &heimdall.EventRecordWithTime{ + EventRecord: heimdall.EventRecord{ + ID: 4, + ChainID: "80002", + Data: hexutil.MustDecode("0x04"), + }, + Time: time.Unix(498, 0), + } + + events := []*heimdall.EventRecordWithTime{event1, event2, event3, event4} + + heimdallClient.EXPECT().FetchStateSyncEvents(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(events, nil).Times(1) + heimdallClient.EXPECT().FetchStateSyncEvents(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*heimdall.EventRecordWithTime{}, nil).AnyTimes() + + var wg sync.WaitGroup + wg.Add(1) + + go func(bridge *Service) { + defer wg.Done() + + err := bridge.Run(ctx) + if err != nil { + if !errors.Is(err, ctx.Err()) { + t.Error(err) + } + + return + } + }(b) + + err = b.store.Prepare(ctx) + require.NoError(t, err) + + replayBlockNum, replayNeeded, err := b.InitialBlockReplayNeeded(ctx) + require.NoError(t, err) + require.True(t, replayNeeded) + require.Equal(t, uint64(0), replayBlockNum) + + genesis := types.NewBlockWithHeader(&types.Header{Time: 1, Number: big.NewInt(0)}) + err = b.ReplayInitialBlock(ctx, genesis) + require.NoError(t, err) + + blocks := getBlocks(t, 10) + err = b.ProcessNewBlocks(ctx, blocks) + require.NoError(t, err) + + res, err := b.EventsWithinTime(ctx, time.Unix(1, 0), time.Unix(500, 0)) + require.NoError(t, err) + require.Len(t, res, 4) + + res, err = b.EventsWithinTime(ctx, time.Unix(50, 0), time.Unix(100, 0)) + require.NoError(t, err) + require.Len(t, res, 2) // have first two events + require.Equal(t, event1Data, res[0].Data()) // check data fields + require.Equal(t, event2Data, res[1].Data()) + + res, err = b.EventsWithinTime(ctx, time.Unix(199, 0), time.Unix(498, 0)) + require.NoError(t, err) + require.Len(t, res, 1) // have third event but not fourth because [A, B) does not include B + require.Equal(t, event3Data, res[0].Data()) // check data fields + + res, err = b.EventsWithinTime(ctx, time.Unix(500, 0), time.Unix(600, 0)) + require.Equal(t, len(res), 0) + require.NoError(t, err) + + cancel() + wg.Wait() +} diff --git a/polygon/bridge/store.go b/polygon/bridge/store.go index 8972de63fb1..77825708486 100644 --- a/polygon/bridge/store.go +++ b/polygon/bridge/store.go @@ -39,6 +39,7 @@ type Store interface { EventTxnToBlockNum(ctx context.Context, borTxHash libcommon.Hash) (uint64, bool, error) Events(ctx context.Context, start, end uint64) ([][]byte, error) BlockEventIdsRange(ctx context.Context, blockHash libcommon.Hash, blockNum uint64) (start uint64, end uint64, ok bool, err error) // [start,end) + EventsByTimeframe(ctx context.Context, timeFrom, timeTo uint64) ([][]byte, error) // [timeFrom, timeTo) PutEventTxnToBlockNum(ctx context.Context, eventTxnToBlockNum map[libcommon.Hash]uint64) error PutEvents(ctx context.Context, events []*heimdall.EventRecordWithTime) error diff --git a/polygon/heimdall/checkpoint.go b/polygon/heimdall/checkpoint.go index 45932d3029e..5a35770e4b7 100644 --- a/polygon/heimdall/checkpoint.go +++ b/polygon/heimdall/checkpoint.go @@ -139,20 +139,28 @@ func (cs checkpoints) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } -type CheckpointResponse struct { +type CheckpointResponseV1 struct { Height string `json:"height"` Result Checkpoint `json:"result"` } +type CheckpointResponseV2 struct { + Checkpoint Checkpoint `json:"checkpoint"` +} + type CheckpointCount struct { Result int64 `json:"result"` } -type CheckpointCountResponse struct { +type CheckpointCountResponseV1 struct { Height string `json:"height"` Result CheckpointCount `json:"result"` } +type CheckpointCountResponseV2 struct { + AckCount string `json:"ack_count"` +} + type CheckpointListResponse struct { Height string `json:"height"` Result checkpoints `json:"result"` diff --git a/polygon/heimdall/client.go b/polygon/heimdall/client.go index bc71f8b6b8c..b15374195dd 100644 --- a/polygon/heimdall/client.go +++ b/polygon/heimdall/client.go @@ -24,12 +24,12 @@ import ( //go:generate mockgen -typed=true -destination=./client_mock.go -package=heimdall . Client type Client interface { FetchStateSyncEvents(ctx context.Context, fromId uint64, to time.Time, limit int) ([]*EventRecordWithTime, error) - FetchStateSyncEvent(ctx context.Context, id uint64) (*EventRecordWithTime, error) FetchLatestSpan(ctx context.Context) (*Span, error) FetchSpan(ctx context.Context, spanID uint64) (*Span, error) FetchSpans(ctx context.Context, page uint64, limit uint64) ([]*Span, error) + FetchChainManagerStatus(ctx context.Context) (*ChainManagerStatus, error) FetchStatus(ctx context.Context) (*Status, error) FetchCheckpoint(ctx context.Context, number int64) (*Checkpoint, error) diff --git a/polygon/heimdall/client_http.go b/polygon/heimdall/client_http.go index 877a80a7187..06e6476fa7d 100644 --- a/polygon/heimdall/client_http.go +++ b/polygon/heimdall/client_http.go @@ -66,6 +66,10 @@ const ( maxRetries = 5 ) +type apiVersioner interface { + Version() HeimdallVersion +} + var _ Client = &HttpClient{} type HttpClient struct { @@ -75,6 +79,7 @@ type HttpClient struct { maxRetries int closeCh chan struct{} logger log.Logger + apiVersioner apiVersioner } type HttpRequest struct { @@ -103,6 +108,12 @@ func WithHttpMaxRetries(maxRetries int) HttpClientOption { } } +func WithApiVersioner(ctx context.Context) HttpClientOption { + return func(client *HttpClient) { + client.apiVersioner = NewVersionMonitor(ctx, client, client.logger, time.Minute) + } +} + func NewHttpClient(urlString string, logger log.Logger, opts ...HttpClientOption) *HttpClient { c := &HttpClient{ urlString: urlString, @@ -121,27 +132,31 @@ func NewHttpClient(urlString string, logger log.Logger, opts ...HttpClientOption } const ( - fetchStateSyncEventsFormat = "from-id=%d&to-time=%d&limit=%d" - fetchStateSyncEventsPath = "clerk/event-record/list" - fetchStateSyncEvent = "clerk/event-record/%s" + fetchStateSyncEventsFormatV1 = "from-id=%d&to-time=%d&limit=%d" + fetchStateSyncEventsFormatV2 = "from_id=%d&to_time=%s&limit=%d" + fetchStateSyncEventsPathV1 = "clerk/event-record/list" + fetchStateSyncEventsPathV2 = "clerk/time" - fetchStatus = "/status" + fetchStatus = "/status" + fetchChainManagerStatus = "/chainmanager/params" fetchCheckpoint = "/checkpoints/%s" fetchCheckpointCount = "/checkpoints/count" fetchCheckpointList = "/checkpoints/list" fetchCheckpointListQueryFormat = "page=%d&limit=%d" - fetchMilestoneAt = "/milestone/%d" - fetchMilestoneLatest = "/milestone/latest" - fetchMilestoneCount = "/milestone/count" + fetchMilestoneAt = "/milestone/%d" + fetchMilestoneLatest = "/milestone/latest" + fetchMilestoneCountV1 = "/milestone/count" + fetchMilestoneCountV2 = "/milestones/count" fetchLastNoAckMilestone = "/milestone/lastNoAck" fetchNoAckMilestone = "/milestone/noAck/%s" fetchMilestoneID = "/milestone/ID/%s" - fetchSpanFormat = "bor/span/%d" - fetchSpanLatest = "bor/latest-span" + fetchSpanLatestV1 = "bor/latest-span" + fetchSpanLatestV2 = "bor/spans/latest" + fetchSpanListFormat = "page=%d&limit=%d" // max limit = 150 fetchSpanListPath = "bor/span/list" ) @@ -149,8 +164,53 @@ const ( func (c *HttpClient) FetchStateSyncEvents(ctx context.Context, fromID uint64, to time.Time, limit int) ([]*EventRecordWithTime, error) { eventRecords := make([]*EventRecordWithTime, 0) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + for { + url, err := stateSyncListURLv2(c.urlString, fromID, to.Unix()) + if err != nil { + return nil, err + } + + c.logger.Trace(heimdallLogPrefix("Fetching state sync events"), "queryParams", url.RawQuery) + + reqCtx := withRequestType(ctx, stateSyncRequest) + + response, err := FetchWithRetry[StateSyncEventsResponseV2](reqCtx, c, url, c.logger) + if err != nil { + if errors.Is(err, ErrNoResponse) { + // for more info check https://github.com/maticnetwork/heimdall/pull/993 + c.logger.Warn( + heimdallLogPrefix("check heimdall logs to see if it is in sync - no response when querying state sync events"), + "path", url.Path, + "queryParams", url.RawQuery, + ) + } + return nil, err + } + + if response == nil || response.EventRecords == nil { + // status 204 + break + } + + eventRecords = append(eventRecords, response.EventRecords...) + + if len(response.EventRecords) < StateEventsFetchLimit || (limit > 0 && len(eventRecords) >= limit) { + break + } + + fromID += uint64(StateEventsFetchLimit) + } + + sort.SliceStable(eventRecords, func(i, j int) bool { + return eventRecords[i].ID < eventRecords[j].ID + }) + + return eventRecords, nil + } + for { - url, err := stateSyncListURL(c.urlString, fromID, to.Unix()) + url, err := stateSyncListURLv1(c.urlString, fromID, to.Unix()) if err != nil { return nil, err } @@ -159,7 +219,7 @@ func (c *HttpClient) FetchStateSyncEvents(ctx context.Context, fromID uint64, to reqCtx := withRequestType(ctx, stateSyncRequest) - response, err := FetchWithRetry[StateSyncEventsResponse](reqCtx, c, url, c.logger) + response, err := FetchWithRetry[StateSyncEventsResponseV1](reqCtx, c, url, c.logger) if err != nil { if errors.Is(err, ErrNoResponse) { // for more info check https://github.com/maticnetwork/heimdall/pull/993 @@ -193,41 +253,29 @@ func (c *HttpClient) FetchStateSyncEvents(ctx context.Context, fromID uint64, to return eventRecords, nil } -func (c *HttpClient) FetchStateSyncEvent(ctx context.Context, id uint64) (*EventRecordWithTime, error) { - url, err := stateSyncURL(c.urlString, id) - - if err != nil { - return nil, err - } - - ctx = withRequestType(ctx, stateSyncRequest) - - isRecoverableError := func(err error) bool { - return !strings.Contains(err.Error(), "could not get state record; No record found") - } +func (c *HttpClient) FetchLatestSpan(ctx context.Context) (*Span, error) { + ctx = withRequestType(ctx, spanRequest) - response, err := FetchWithRetryEx[StateSyncEventResponse](ctx, c, url, isRecoverableError, c.logger) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + url, err := makeURL(c.urlString, fetchSpanLatestV2, "") + if err != nil { + return nil, err + } - if err != nil { - if strings.Contains(err.Error(), "could not get state record; No record found") { - return nil, ErrEventRecordNotFound + response, err := FetchWithRetry[SpanResponseV2](ctx, c, url, c.logger) + if err != nil { + return nil, err } - return nil, err + return response.ToSpan() } - return &response.Result, nil -} - -func (c *HttpClient) FetchLatestSpan(ctx context.Context) (*Span, error) { - url, err := latestSpanURL(c.urlString) + url, err := makeURL(c.urlString, fetchSpanLatestV1, "") if err != nil { return nil, err } - ctx = withRequestType(ctx, spanRequest) - - response, err := FetchWithRetry[SpanResponse](ctx, c, url, c.logger) + response, err := FetchWithRetry[SpanResponseV1](ctx, c, url, c.logger) if err != nil { return nil, err } @@ -236,14 +284,29 @@ func (c *HttpClient) FetchLatestSpan(ctx context.Context) (*Span, error) { } func (c *HttpClient) FetchSpan(ctx context.Context, spanID uint64) (*Span, error) { - url, err := spanURL(c.urlString, spanID) + url, err := makeURL(c.urlString, fmt.Sprintf("bor/span/%d", spanID), "") if err != nil { return nil, fmt.Errorf("%w, spanID=%d", err, spanID) } ctx = withRequestType(ctx, spanRequest) - response, err := FetchWithRetry[SpanResponse](ctx, c, url, c.logger) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + url, err = makeURL(c.urlString, fmt.Sprintf("bor/spans/%d", spanID), "") + if err != nil { + return nil, fmt.Errorf("%w, spanID=%d", err, spanID) + } + + response, err := FetchWithRetry[SpanResponseV2](ctx, c, url, c.logger) + if err != nil { + return nil, fmt.Errorf("%w, spanID=%d", err, spanID) + } + + return response.ToSpan() + + } + + response, err := FetchWithRetry[SpanResponseV1](ctx, c, url, c.logger) if err != nil { return nil, fmt.Errorf("%w, spanID=%d", err, spanID) } @@ -276,7 +339,16 @@ func (c *HttpClient) FetchCheckpoint(ctx context.Context, number int64) (*Checkp ctx = withRequestType(ctx, checkpointRequest) - response, err := FetchWithRetry[CheckpointResponse](ctx, c, url, c.logger) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + response, err := FetchWithRetry[CheckpointResponseV2](ctx, c, url, c.logger) + if err != nil { + return nil, err + } + + return &response.Checkpoint, nil + } + + response, err := FetchWithRetry[CheckpointResponseV1](ctx, c, url, c.logger) if err != nil { return nil, err } @@ -338,7 +410,19 @@ func (c *HttpClient) FetchMilestone(ctx context.Context, number int64) (*Milesto return firstNum <= number && number <= firstNum+milestonePruneNumber-1 } - response, err := FetchWithRetryEx[MilestoneResponse](ctx, c, url, isRecoverableError, c.logger) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + response, err := FetchWithRetryEx[MilestoneResponseV2](ctx, c, url, isRecoverableError, c.logger) + if err != nil { + if isInvalidMilestoneIndexError(err) { + return nil, fmt.Errorf("%w: number %d", ErrNotInMilestoneList, number) + } + return nil, err + } + + return &response.Milestone, nil + } + + response, err := FetchWithRetryEx[MilestoneResponseV1](ctx, c, url, isRecoverableError, c.logger) if err != nil { if isInvalidMilestoneIndexError(err) { return nil, fmt.Errorf("%w: number %d", ErrNotInMilestoneList, number) @@ -351,6 +435,17 @@ func (c *HttpClient) FetchMilestone(ctx context.Context, number int64) (*Milesto return &response.Result, nil } +func (c *HttpClient) FetchChainManagerStatus(ctx context.Context) (*ChainManagerStatus, error) { + url, err := chainManagerStatusURL(c.urlString) + if err != nil { + return nil, err + } + + ctx = withRequestType(ctx, statusRequest) + + return FetchWithRetry[ChainManagerStatus](ctx, c, url, c.logger) +} + func (c *HttpClient) FetchStatus(ctx context.Context) (*Status, error) { url, err := statusURL(c.urlString) if err != nil { @@ -359,6 +454,10 @@ func (c *HttpClient) FetchStatus(ctx context.Context) (*Status, error) { ctx = withRequestType(ctx, statusRequest) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + return FetchWithRetry[Status](ctx, c, url, c.logger) + } + response, err := FetchWithRetry[StatusResponse](ctx, c, url, c.logger) if err != nil { return nil, err @@ -376,7 +475,21 @@ func (c *HttpClient) FetchCheckpointCount(ctx context.Context) (int64, error) { ctx = withRequestType(ctx, checkpointCountRequest) - response, err := FetchWithRetry[CheckpointCountResponse](ctx, c, url, c.logger) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + response, err := FetchWithRetry[CheckpointCountResponseV2](ctx, c, url, c.logger) + if err != nil { + return 0, err + } + + count, err := strconv.Atoi(response.AckCount) + if err != nil { + return 0, err + } + + return int64(count), nil + } + + response, err := FetchWithRetry[CheckpointCountResponseV1](ctx, c, url, c.logger) if err != nil { return 0, err } @@ -386,14 +499,33 @@ func (c *HttpClient) FetchCheckpointCount(ctx context.Context) (int64, error) { // FetchMilestoneCount fetches the milestone count from heimdall func (c *HttpClient) FetchMilestoneCount(ctx context.Context) (int64, error) { - url, err := milestoneCountURL(c.urlString) + url, err := makeURL(c.urlString, fetchMilestoneCountV1, "") if err != nil { return 0, err } ctx = withRequestType(ctx, milestoneCountRequest) - response, err := FetchWithRetry[MilestoneCountResponse](ctx, c, url, c.logger) + if c.apiVersioner != nil && c.apiVersioner.Version() == HeimdallV2 { + url, err := makeURL(c.urlString, fetchMilestoneCountV2, "") + if err != nil { + return 0, err + } + + response, err := FetchWithRetry[MilestoneCountResponseV2](ctx, c, url, c.logger) + if err != nil { + return 0, err + } + + count, err := strconv.Atoi(response.Count) + if err != nil { + return 0, err + } + + return int64(count), nil + } + + response, err := FetchWithRetry[MilestoneCountResponseV1](ctx, c, url, c.logger) if err != nil { return 0, err } @@ -569,25 +701,21 @@ func Fetch[T any](ctx context.Context, request *HttpRequest, logger log.Logger) return result, nil } -func spanURL(urlString string, spanID uint64) (*url.URL, error) { - return makeURL(urlString, fmt.Sprintf(fetchSpanFormat, spanID), "") -} - func spanListURL(urlString string, page, limit uint64) (*url.URL, error) { return makeURL(urlString, fetchSpanListPath, fmt.Sprintf(fetchSpanListFormat, page, limit)) } -func latestSpanURL(urlString string) (*url.URL, error) { - return makeURL(urlString, fetchSpanLatest, "") +func stateSyncListURLv1(urlString string, fromID uint64, to int64) (*url.URL, error) { + queryParams := fmt.Sprintf(fetchStateSyncEventsFormatV1, fromID, to, StateEventsFetchLimit) + return makeURL(urlString, fetchStateSyncEventsPathV1, queryParams) } -func stateSyncListURL(urlString string, fromID uint64, to int64) (*url.URL, error) { - queryParams := fmt.Sprintf(fetchStateSyncEventsFormat, fromID, to, StateEventsFetchLimit) - return makeURL(urlString, fetchStateSyncEventsPath, queryParams) -} +func stateSyncListURLv2(urlString string, fromID uint64, to int64) (*url.URL, error) { + t := time.Unix(to, 0).UTC() + formattedTime := t.Format(time.RFC3339Nano) -func stateSyncURL(urlString string, id uint64) (*url.URL, error) { - return makeURL(urlString, fmt.Sprintf(fetchStateSyncEvent, strconv.FormatUint(id, 10)), "") + queryParams := fmt.Sprintf(fetchStateSyncEventsFormatV2, fromID, formattedTime, StateEventsFetchLimit) + return makeURL(urlString, fetchStateSyncEventsPathV2, queryParams) } func checkpointURL(urlString string, number int64) (*url.URL, error) { @@ -605,6 +733,10 @@ func checkpointCountURL(urlString string) (*url.URL, error) { return makeURL(urlString, fetchCheckpointCount, "") } +func chainManagerStatusURL(urlString string) (*url.URL, error) { + return makeURL(urlString, fetchChainManagerStatus, "") +} + func statusURL(urlString string) (*url.URL, error) { return makeURL(urlString, fetchStatus, "") } @@ -620,10 +752,6 @@ func milestoneURL(urlString string, number int64) (*url.URL, error) { return makeURL(urlString, fmt.Sprintf(fetchMilestoneAt, number), "") } -func milestoneCountURL(urlString string) (*url.URL, error) { - return makeURL(urlString, fetchMilestoneCount, "") -} - func lastNoAckMilestoneURL(urlString string) (*url.URL, error) { return makeURL(urlString, fetchLastNoAckMilestone, "") } diff --git a/polygon/heimdall/client_mock.go b/polygon/heimdall/client_mock.go index a8f3baf9089..3c486f40252 100644 --- a/polygon/heimdall/client_mock.go +++ b/polygon/heimdall/client_mock.go @@ -77,6 +77,45 @@ func (c *MockClientCloseCall) DoAndReturn(f func()) *MockClientCloseCall { return c } +// FetchChainManagerStatus mocks base method. +func (m *MockClient) FetchChainManagerStatus(ctx context.Context) (*ChainManagerStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchChainManagerStatus", ctx) + ret0, _ := ret[0].(*ChainManagerStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchChainManagerStatus indicates an expected call of FetchChainManagerStatus. +func (mr *MockClientMockRecorder) FetchChainManagerStatus(ctx any) *MockClientFetchChainManagerStatusCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchChainManagerStatus", reflect.TypeOf((*MockClient)(nil).FetchChainManagerStatus), ctx) + return &MockClientFetchChainManagerStatusCall{Call: call} +} + +// MockClientFetchChainManagerStatusCall wrap *gomock.Call +type MockClientFetchChainManagerStatusCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockClientFetchChainManagerStatusCall) Return(arg0 *ChainManagerStatus, arg1 error) *MockClientFetchChainManagerStatusCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockClientFetchChainManagerStatusCall) Do(f func(context.Context) (*ChainManagerStatus, error)) *MockClientFetchChainManagerStatusCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockClientFetchChainManagerStatusCall) DoAndReturn(f func(context.Context) (*ChainManagerStatus, error)) *MockClientFetchChainManagerStatusCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // FetchCheckpoint mocks base method. func (m *MockClient) FetchCheckpoint(ctx context.Context, number int64) (*Checkpoint, error) { m.ctrl.T.Helper() @@ -543,45 +582,6 @@ func (c *MockClientFetchSpansCall) DoAndReturn(f func(context.Context, uint64, u return c } -// FetchStateSyncEvent mocks base method. -func (m *MockClient) FetchStateSyncEvent(ctx context.Context, id uint64) (*EventRecordWithTime, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FetchStateSyncEvent", ctx, id) - ret0, _ := ret[0].(*EventRecordWithTime) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// FetchStateSyncEvent indicates an expected call of FetchStateSyncEvent. -func (mr *MockClientMockRecorder) FetchStateSyncEvent(ctx, id any) *MockClientFetchStateSyncEventCall { - mr.mock.ctrl.T.Helper() - call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchStateSyncEvent", reflect.TypeOf((*MockClient)(nil).FetchStateSyncEvent), ctx, id) - return &MockClientFetchStateSyncEventCall{Call: call} -} - -// MockClientFetchStateSyncEventCall wrap *gomock.Call -type MockClientFetchStateSyncEventCall struct { - *gomock.Call -} - -// Return rewrite *gomock.Call.Return -func (c *MockClientFetchStateSyncEventCall) Return(arg0 *EventRecordWithTime, arg1 error) *MockClientFetchStateSyncEventCall { - c.Call = c.Call.Return(arg0, arg1) - return c -} - -// Do rewrite *gomock.Call.Do -func (c *MockClientFetchStateSyncEventCall) Do(f func(context.Context, uint64) (*EventRecordWithTime, error)) *MockClientFetchStateSyncEventCall { - c.Call = c.Call.Do(f) - return c -} - -// DoAndReturn rewrite *gomock.Call.DoAndReturn -func (c *MockClientFetchStateSyncEventCall) DoAndReturn(f func(context.Context, uint64) (*EventRecordWithTime, error)) *MockClientFetchStateSyncEventCall { - c.Call = c.Call.DoAndReturn(f) - return c -} - // FetchStateSyncEvents mocks base method. func (m *MockClient) FetchStateSyncEvents(ctx context.Context, fromId uint64, to time.Time, limit int) ([]*EventRecordWithTime, error) { m.ctrl.T.Helper() diff --git a/polygon/heimdall/event_record.go b/polygon/heimdall/event_record.go index 6381f7970d0..6bc6e79f5b2 100644 --- a/polygon/heimdall/event_record.go +++ b/polygon/heimdall/event_record.go @@ -78,6 +78,12 @@ func (e *EventRecordWithTime) MarshallIdBytes() []byte { return id[:] } +func (e *EventRecordWithTime) MarshallTimeBytes() []byte { + var t [8]byte + binary.BigEndian.PutUint64(t[:], uint64(e.Time.Unix())) + return t[:] +} + func (e *EventRecordWithTime) MarshallBytes() ([]byte, error) { eventRecordWithoutTime := e.BuildEventRecord() rlpBytes, err := rlp.EncodeToBytes(eventRecordWithoutTime) @@ -122,14 +128,13 @@ func (e *EventRecordWithTime) UnmarshallBytes(v []byte) error { return nil } -type StateSyncEventsResponse struct { +type StateSyncEventsResponseV1 struct { Height string `json:"height"` Result []*EventRecordWithTime `json:"result"` } -type StateSyncEventResponse struct { - Height string `json:"height"` - Result EventRecordWithTime `json:"result"` +type StateSyncEventsResponseV2 struct { + EventRecords []*EventRecordWithTime `json:"event_records"` } var methodId []byte = borabi.StateReceiverContractABI().Methods["commitState"].ID diff --git a/polygon/heimdall/milestone.go b/polygon/heimdall/milestone.go index 25d632e7b4e..f6999ed6962 100644 --- a/polygon/heimdall/milestone.go +++ b/polygon/heimdall/milestone.go @@ -130,20 +130,28 @@ func (m *Milestone) UnmarshalJSON(b []byte) error { return nil } -type MilestoneResponse struct { +type MilestoneResponseV1 struct { Height string `json:"height"` Result Milestone `json:"result"` } +type MilestoneResponseV2 struct { + Milestone Milestone `json:"milestone"` +} + type MilestoneCount struct { Count int64 `json:"count"` } -type MilestoneCountResponse struct { +type MilestoneCountResponseV1 struct { Height string `json:"height"` Result MilestoneCount `json:"result"` } +type MilestoneCountResponseV2 struct { + Count string `json:"count"` +} + type MilestoneLastNoAck struct { Result string `json:"result"` } diff --git a/polygon/heimdall/scraper.go b/polygon/heimdall/scraper.go index 3cfb97450dc..fbb36dd65fc 100644 --- a/polygon/heimdall/scraper.go +++ b/polygon/heimdall/scraper.go @@ -18,7 +18,6 @@ package heimdall import ( "context" - "errors" "fmt" "time" @@ -149,18 +148,10 @@ func (s *Scraper[TEntity]) RegisterObserver(observer func([]TEntity)) event.Unre return s.observers.Register(observer) } -func (s *Scraper[TEntity]) Synchronize(ctx context.Context) (TEntity, error) { +func (s *Scraper[TEntity]) Synchronize(ctx context.Context) (TEntity, bool, error) { if err := s.syncEvent.Wait(ctx); err != nil { - return generics.Zero[TEntity](), err + return generics.Zero[TEntity](), false, err } - last, ok, err := s.store.LastEntity(ctx) - if err != nil { - return generics.Zero[TEntity](), err - } - if !ok { - return generics.Zero[TEntity](), errors.New("unexpected last entity not available") - } - - return last, nil + return s.store.LastEntity(ctx) } diff --git a/polygon/heimdall/service.go b/polygon/heimdall/service.go index 4690e69c041..bf3c42ba615 100644 --- a/polygon/heimdall/service.go +++ b/polygon/heimdall/service.go @@ -168,12 +168,12 @@ func (s *Service) Span(ctx context.Context, id uint64) (*Span, bool, error) { return s.reader.Span(ctx, id) } -func (s *Service) SynchronizeCheckpoints(ctx context.Context) (*Checkpoint, error) { +func (s *Service) SynchronizeCheckpoints(ctx context.Context) (*Checkpoint, bool, error) { s.logger.Info(heimdallLogPrefix("synchronizing checkpoints...")) return s.checkpointScraper.Synchronize(ctx) } -func (s *Service) SynchronizeMilestones(ctx context.Context) (*Milestone, error) { +func (s *Service) SynchronizeMilestones(ctx context.Context) (*Milestone, bool, error) { s.logger.Info(heimdallLogPrefix("synchronizing milestones...")) return s.milestoneScraper.Synchronize(ctx) } @@ -205,9 +205,13 @@ func (s *Service) SynchronizeSpans(ctx context.Context, blockNum uint64) error { } func (s *Service) synchronizeSpans(ctx context.Context) error { - if _, err := s.spanScraper.Synchronize(ctx); err != nil { + _, ok, err := s.spanScraper.Synchronize(ctx) + if err != nil { return err } + if !ok { + return errors.New("unexpected last entity not available") + } if err := s.spanBlockProducersTracker.Synchronize(ctx); err != nil { return err diff --git a/polygon/heimdall/service_test.go b/polygon/heimdall/service_test.go index 8ed65d6fd1d..d8a1367819e 100644 --- a/polygon/heimdall/service_test.go +++ b/polygon/heimdall/service_test.go @@ -185,12 +185,16 @@ func (suite *ServiceTestSuite) SetupSuite() { return suite.service.Run(suite.ctx) }) - lastMilestone, err := suite.service.SynchronizeMilestones(suite.ctx) + lastMilestone, ok, err := suite.service.SynchronizeMilestones(suite.ctx) require.NoError(suite.T(), err) + require.True(suite.T(), ok) require.Equal(suite.T(), suite.expectedLastMilestone, uint64(lastMilestone.Id)) - lastCheckpoint, err := suite.service.SynchronizeCheckpoints(suite.ctx) + + lastCheckpoint, ok, err := suite.service.SynchronizeCheckpoints(suite.ctx) require.NoError(suite.T(), err) + require.True(suite.T(), ok) require.Equal(suite.T(), suite.expectedLastCheckpoint, uint64(lastCheckpoint.Id)) + err = suite.service.SynchronizeSpans(suite.ctx, math.MaxInt) require.NoError(suite.T(), err) } diff --git a/polygon/heimdall/snapshot_store.go b/polygon/heimdall/snapshot_store.go index d4f6d362461..2736cfb54e2 100644 --- a/polygon/heimdall/snapshot_store.go +++ b/polygon/heimdall/snapshot_store.go @@ -199,7 +199,7 @@ func (s *SpanSnapshotStore) RangeFromBlockNum(ctx context.Context, startBlockNum } func (s *SpanSnapshotStore) ValidateSnapshots(ctx context.Context, logger log.Logger, failFast bool) error { - return validateSnapshots(ctx, logger, s.EntityStore, failFast, s.snapshots, s.SnapType(), generics.New[Span]) + return validateSnapshots(ctx, logger, s.EntityStore, failFast, s.snapshots, s.SnapType(), generics.New[Span], 0, true) } type MilestoneSnapshotStore struct { @@ -330,7 +330,7 @@ func (s *MilestoneSnapshotStore) RangeFromBlockNum(ctx context.Context, startBlo } func (s *MilestoneSnapshotStore) ValidateSnapshots(ctx context.Context, logger log.Logger, failFast bool) error { - return validateSnapshots(ctx, logger, s.EntityStore, failFast, s.snapshots, s.SnapType(), generics.New[Milestone]) + return validateSnapshots(ctx, logger, s.EntityStore, failFast, s.snapshots, s.SnapType(), generics.New[Milestone], 1, true) } type CheckpointSnapshotStore struct { @@ -451,7 +451,7 @@ func (s *CheckpointSnapshotStore) RangeFromBlockNum(ctx context.Context, startBl } func (s *CheckpointSnapshotStore) ValidateSnapshots(ctx context.Context, logger log.Logger, failFast bool) error { - return validateSnapshots(ctx, logger, s.EntityStore, failFast, s.snapshots, s.SnapType(), generics.New[Checkpoint]) + return validateSnapshots(ctx, logger, s.EntityStore, failFast, s.snapshots, s.SnapType(), generics.New[Checkpoint], 1, true) } func validateSnapshots[T Entity]( @@ -462,6 +462,8 @@ func validateSnapshots[T Entity]( snaps *RoSnapshots, t snaptype.Type, makeEntity func() T, + firstEntityId uint64, + alsoCheckDb bool, ) error { tx := snaps.ViewType(t) defer tx.Close() @@ -472,7 +474,7 @@ func validateSnapshots[T Entity]( } var accumulatedErr error - var prev *T + expectedId := firstEntityId for _, seg := range segs { idx := seg.Src().Index() if idx == nil || idx.KeyCount() == 0 { @@ -495,16 +497,11 @@ func validateSnapshots[T Entity]( "end", entity.BlockNumRange().End, "segmentFrom", seg.From(), "segmentTo", seg.To(), + "expectedId", expectedId, ) - if prev == nil { - prev = &entity - continue - } - - expectedId := (*prev).RawId() + 1 if expectedId == entity.RawId() { - prev = &entity + expectedId++ continue } @@ -517,19 +514,23 @@ func validateSnapshots[T Entity]( return accumulatedErr } - prev = &entity + expectedId = entity.RawId() + 1 } } + if !alsoCheckDb { + return accumulatedErr + } + // make sure snapshots connect with data in the db and there are no gaps at all lastInDb, ok, err := dbStore.LastEntityId(ctx) if err != nil { return err } - if !ok || prev == nil { - return nil + if !ok { + return accumulatedErr } - for i := (*prev).RawId() + 1; i <= lastInDb; i++ { + for i := expectedId; i <= lastInDb; i++ { _, ok, err := dbStore.Entity(ctx, i) if err != nil { return err @@ -538,6 +539,10 @@ func validateSnapshots[T Entity]( continue } // we've found a gap between snapshots and db + if accumulatedErr == nil { + accumulatedErr = errors.New("missing entities") + } + accumulatedErr = fmt.Errorf("%w: db [%d]", accumulatedErr, i) if failFast { return accumulatedErr diff --git a/polygon/heimdall/span.go b/polygon/heimdall/span.go index 2a045aed19f..ae851f9bcfa 100644 --- a/polygon/heimdall/span.go +++ b/polygon/heimdall/span.go @@ -17,8 +17,11 @@ package heimdall import ( + "strconv" + "github.com/google/btree" + "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon/polygon/bor/valset" ) @@ -70,11 +73,112 @@ func (s *Span) Producers() []*valset.Validator { return res } -type SpanResponse struct { +type SpanResponseV1 struct { Height string `json:"height"` Result Span `json:"result"` } +type validator struct { + ValID string `json:"val_id"` + Address string `json:"signer"` + VotingPower string `json:"voting_power"` + ProposerPriority string `json:"proposer_priority"` +} + +func (v *validator) toValidator() (valset.Validator, error) { + id, err := strconv.Atoi(v.ValID) + if err != nil { + return valset.Validator{}, err + } + + votingPower, err := strconv.Atoi(v.VotingPower) + if err != nil { + return valset.Validator{}, err + } + + proposerPriority, err := strconv.Atoi(v.VotingPower) + if err != nil { + return valset.Validator{}, err + } + + rr := valset.Validator{ + ID: uint64(id), + Address: common.HexToAddress(v.Address), + VotingPower: int64(votingPower), + ProposerPriority: int64(proposerPriority), + } + + return rr, nil +} + +type SpanResponseV2 struct { + Span struct { + ID string `json:"id"` + StartBlock string `json:"start_block"` + EndBlock string `json:"end_block"` + ValidatorSet struct { + Validators []validator `json:"validators"` + Proposer validator `json:"proposer"` + } `json:"validator_set"` + SelectedProducers []validator `json:"selected_producers"` + ChainID string `json:"chain_id"` + } `json:"span"` +} + +func (r *SpanResponseV2) ToSpan() (*Span, error) { + id, err := strconv.Atoi(r.Span.ID) + if err != nil { + return nil, err + } + + startBlock, err := strconv.Atoi(r.Span.StartBlock) + if err != nil { + return nil, err + } + + endBlock, err := strconv.Atoi(r.Span.EndBlock) + if err != nil { + return nil, err + } + + proposer, err := r.Span.ValidatorSet.Proposer.toValidator() + if err != nil { + return nil, err + } + + s := &Span{ + Id: SpanId(id), + StartBlock: uint64(startBlock), + EndBlock: uint64(endBlock), + ValidatorSet: valset.ValidatorSet{ + Validators: make([]*valset.Validator, 0, len(r.Span.ValidatorSet.Validators)), + Proposer: &proposer, + }, + SelectedProducers: make([]valset.Validator, 0, len(r.Span.SelectedProducers)), + ChainID: r.Span.ChainID, + } + + for i := range r.Span.ValidatorSet.Validators { + toAppend, err := r.Span.ValidatorSet.Validators[i].toValidator() + if err != nil { + return nil, err + } + + s.ValidatorSet.Validators = append(s.ValidatorSet.Validators, &toAppend) + } + + for i := range r.Span.SelectedProducers { + toAppend, err := r.Span.SelectedProducers[i].toValidator() + if err != nil { + return nil, err + } + + s.SelectedProducers = append(s.SelectedProducers, toAppend) + } + + return s, nil +} + type spans []*Span func (s spans) Len() int { diff --git a/polygon/heimdall/status.go b/polygon/heimdall/status.go index 6e63fb5abca..d8ba6bca6af 100644 --- a/polygon/heimdall/status.go +++ b/polygon/heimdall/status.go @@ -17,14 +17,21 @@ package heimdall type Status struct { - LatestBlockHash string `json:"latest_block_hash"` - LatestAppHash string `json:"latest_app_hash"` - LatestBlockHeight string `json:"latest_block_height"` - LatestBlockTime string `json:"latest_block_time"` - CatchingUp bool `json:"catching_up"` + LatestBlockHash string `json:"latest_block_hash"` + LatestAppHash string `json:"latest_app_hash"` + LatestBlockTime string `json:"latest_block_time"` + CatchingUp bool `json:"catching_up"` } type StatusResponse struct { Height string `json:"height"` Result Status `json:"result"` } + +type ChainManagerStatus struct { + Params struct { + ChainParams struct { + PolTokenAddress *string `json:"pol_token_address,omitempty"` + } `json:"chain_params"` + } `json:"params"` +} diff --git a/polygon/heimdall/version_monitor.go b/polygon/heimdall/version_monitor.go new file mode 100644 index 00000000000..beb8c8291eb --- /dev/null +++ b/polygon/heimdall/version_monitor.go @@ -0,0 +1,84 @@ +package heimdall + +import ( + "context" + "sync" + "time" + + "github.com/erigontech/erigon-lib/log/v3" +) + +type HeimdallVersion int64 + +const ( + HeimdallV1 HeimdallVersion = iota + HeimdallV2 +) + +type heimdallClient interface { + FetchChainManagerStatus(ctx context.Context) (*ChainManagerStatus, error) +} + +type versionMonitor struct { + ctx context.Context + currentVersion HeimdallVersion + + heimdall heimdallClient + logger log.Logger + + frequency time.Duration + m sync.Mutex + once sync.Once +} + +func NewVersionMonitor(ctx context.Context, heimdallClient heimdallClient, logger log.Logger, frequency time.Duration) *versionMonitor { + return &versionMonitor{ + ctx: ctx, + heimdall: heimdallClient, + currentVersion: HeimdallV1, + frequency: frequency, + logger: logger, + } +} + +func (vm *versionMonitor) Run() { + checkEvery := time.NewTicker(vm.frequency) + defer checkEvery.Stop() + + for { + select { + case <-checkEvery.C: + vm.resolveVersion() + vm.once.Do(func() {}) // no need to resolve version in Version() function anymore + case <-vm.ctx.Done(): + return + } + } +} + +func (vm *versionMonitor) Version() HeimdallVersion { + vm.once.Do(vm.resolveVersion) + + vm.m.Lock() + defer vm.m.Unlock() + + return vm.currentVersion +} + +func (vm *versionMonitor) resolveVersion() { + status, err := vm.heimdall.FetchChainManagerStatus(vm.ctx) + if err != nil { + vm.logger.Error("Failed attempt to resolve heimdall version", "err", err) + return + } + + vm.m.Lock() + defer vm.m.Unlock() + + // We should monitor upgrade and downgrade both, because it is a valid scenario + if status.Params.ChainParams.PolTokenAddress != nil { + vm.currentVersion = HeimdallV2 + } else { + vm.currentVersion = HeimdallV1 + } +} diff --git a/polygon/heimdall/version_monitor_test.go b/polygon/heimdall/version_monitor_test.go new file mode 100644 index 00000000000..d42cb83d142 --- /dev/null +++ b/polygon/heimdall/version_monitor_test.go @@ -0,0 +1,137 @@ +package heimdall_test + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon/polygon/heimdall" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestVersioMonitorHeimdallV2(t *testing.T) { + ctrl := gomock.NewController(t) + heimdallClient := heimdall.NewMockClient(ctrl) + + status := &heimdall.ChainManagerStatus{} + status.Params.ChainParams.PolTokenAddress = new(string) + + heimdallClient. + EXPECT(). + FetchChainManagerStatus(gomock.Any()). + Return(status, nil) + + monitor := heimdall.NewVersionMonitor(context.TODO(), heimdallClient, log.New(), time.Minute) + resolved := monitor.Version() + + require.Equal(t, resolved, heimdall.HeimdallV2) +} + +func TestVersioMonitorHeimdallV1(t *testing.T) { + ctrl := gomock.NewController(t) + heimdallClient := heimdall.NewMockClient(ctrl) + + status := &heimdall.ChainManagerStatus{} + + heimdallClient. + EXPECT(). + FetchChainManagerStatus(gomock.Any()). + Return(status, nil) + + monitor := heimdall.NewVersionMonitor(context.TODO(), heimdallClient, log.New(), time.Minute) + resolved := monitor.Version() + + require.Equal(t, resolved, heimdall.HeimdallV1) +} + +func TestVersioMonitorHeimdallUpgrade(t *testing.T) { + ctx, clean := context.WithTimeout(context.Background(), time.Minute) + defer clean() + + ctrl := gomock.NewController(t) + heimdallClient := heimdall.NewMockClient(ctrl) + + timeNow := time.Now() + var upgradeMonitoredTimes atomic.Int64 + + heimdallClient. + EXPECT(). + FetchChainManagerStatus(gomock.Any()). + DoAndReturn(func(ctx context.Context) (*heimdall.ChainManagerStatus, error) { + status := &heimdall.ChainManagerStatus{} + + if time.Since(timeNow) > time.Second { + status.Params.ChainParams.PolTokenAddress = new(string) + upgradeMonitoredTimes.Add(1) + } + + return status, nil + }).AnyTimes() + + monitor := heimdall.NewVersionMonitor(ctx, heimdallClient, log.New(), 100*time.Millisecond) + go monitor.Run() + + for { + time.Sleep(100 * time.Millisecond) + + resolved := monitor.Version() + + switch upgradeMonitoredTimes.Load() { + case 0: + require.Equal(t, resolved, heimdall.HeimdallV1) // Upgrade has not been happened yet + case 1: + // Upgrade happened and monitored but race still possible to happen. Let's skip the check + default: + require.Equal(t, resolved, heimdall.HeimdallV2) // Upgrade happened and monitored twice or more -> it was updated in the monitor + return + } + } +} + +func TestVersioMonitorHeimdallDowngrade(t *testing.T) { + ctx, clean := context.WithTimeout(context.Background(), time.Minute) + defer clean() + + ctrl := gomock.NewController(t) + heimdallClient := heimdall.NewMockClient(ctrl) + + timeNow := time.Now() + var downgradeMonitoredTimes atomic.Int64 + + heimdallClient. + EXPECT(). + FetchChainManagerStatus(gomock.Any()). + DoAndReturn(func(ctx context.Context) (*heimdall.ChainManagerStatus, error) { + status := &heimdall.ChainManagerStatus{} + status.Params.ChainParams.PolTokenAddress = new(string) + + if time.Since(timeNow) > time.Second { + status.Params.ChainParams.PolTokenAddress = nil + downgradeMonitoredTimes.Add(1) + } + + return status, nil + }).AnyTimes() + + monitor := heimdall.NewVersionMonitor(ctx, heimdallClient, log.New(), 100*time.Millisecond) + go monitor.Run() + + for { + time.Sleep(100 * time.Millisecond) + + resolved := monitor.Version() + + switch downgradeMonitoredTimes.Load() { + case 0: + require.Equal(t, resolved, heimdall.HeimdallV2) // Downgrade has not been happened yet + case 1: + // Downgrade happened and monitored but race still possible to happen. Let's skip the check + default: + require.Equal(t, resolved, heimdall.HeimdallV1) // Downgrade happened and monitored twice or more -> it was updated in the monitor + return + } + } +} diff --git a/polygon/sync/canonical_chain_builder.go b/polygon/sync/canonical_chain_builder.go index 6ead119f3f9..b2ca2cf4449 100644 --- a/polygon/sync/canonical_chain_builder.go +++ b/polygon/sync/canonical_chain_builder.go @@ -276,17 +276,20 @@ func (ccb *CanonicalChainBuilder) Connect(ctx context.Context, headers []*types. // attach nodes for the new headers for i, header := range headers { + // IMPORTANT: in case of errors, remember to return the connected headers so far in the loop for insertion + // by the caller because we've already mutated the in-mem tree within the previous loop iterations + processed := headers[:i] if (header.Number == nil) || (header.Number.Uint64() != parent.header.Number.Uint64()+1) { - return nil, errors.New("CanonicalChainBuilder.Connect: invalid header.Number") + return processed, fmt.Errorf("can't connect %s: invalid number: expected %d", header.Number, parent.header.Number.Uint64()+1) } if err := ccb.headerValidator.ValidateHeader(ctx, header, parent.header, time.Now()); err != nil { - return nil, fmt.Errorf("CanonicalChainBuilder.Connect: invalid header error %w", err) + return processed, fmt.Errorf("can't connect %s: invalid header: error %w", header.Number, err) } difficulty, err := ccb.difficultyCalc.HeaderDifficulty(ctx, header) if err != nil { - return nil, fmt.Errorf("CanonicalChainBuilder.Connect: header difficulty error %w", err) + return processed, fmt.Errorf("can't connect %s: header difficulty error %w", header.Number, err) } if (header.Difficulty == nil) || (header.Difficulty.Uint64() != difficulty) { err := &bor.WrongDifficultyError{ @@ -295,12 +298,12 @@ func (ccb *CanonicalChainBuilder) Connect(ctx context.Context, headers []*types. Actual: header.Difficulty.Uint64(), Signer: []byte{}, } - return nil, err + return processed, err } slot := producerSlotIndex(difficulty) if _, ok := parent.children[slot]; ok { - return nil, errors.New("CanonicalChainBuilder.Connect: producer slot is already filled by a different header") + return processed, fmt.Errorf("can't connect %s: producer slot is already filled by a different header", header.Number) } node := &forkTreeNode{ diff --git a/polygon/sync/service.go b/polygon/sync/service.go index 1b793df1323..1e2034c5665 100644 --- a/polygon/sync/service.go +++ b/polygon/sync/service.go @@ -51,6 +51,7 @@ func NewService( heimdallService *heimdall.Service, notifications *shards.Notifications, engineAPISwitcher EngineAPISwitcher, + minedBlockReg MinedBlockObserverRegistrar, ) *Service { borConfig := chainConfig.Bor.(*borcfg.BorConfig) @@ -77,7 +78,7 @@ func NewService( blockLimit, ) ccBuilderFactory := NewCanonicalChainBuilderFactory(chainConfig, borConfig, heimdallService, signaturesCache) - events := NewTipEvents(logger, p2pService, heimdallService) + events := NewTipEvents(logger, p2pService, heimdallService, minedBlockReg) sync := NewSync( config, logger, diff --git a/polygon/sync/sync.go b/polygon/sync/sync.go index 14a160f06c1..eacd963db33 100644 --- a/polygon/sync/sync.go +++ b/polygon/sync/sync.go @@ -34,10 +34,19 @@ import ( "github.com/erigontech/erigon/turbo/shards" ) +// If there are no waypoints from Heimdall (including in our local database), we won't be able to rely on the last received waypoint +// to determine the root of the canonical chain builder tree. However, we heuristically know the maximum expected height of such a tree. +// Therefore, given a descendant (tip), we can select a block that lags behind it by this constant value and consider it as the root. +// If we happen to choose an older root, it's not a problem since this only temporarily affects the tree size. +// +// Waypoints may be absent in case if it's an early stage of the chain's evolution, starting from the genesis block. +// The current constant value is chosen based on observed metrics in production as twice the doubled value of the maximum observed waypoint length. +const maxFinalizationHeight = 512 + type heimdallSynchronizer interface { IsCatchingUp(ctx context.Context) (bool, error) - SynchronizeCheckpoints(ctx context.Context) (latest *heimdall.Checkpoint, err error) - SynchronizeMilestones(ctx context.Context) (latest *heimdall.Milestone, err error) + SynchronizeCheckpoints(ctx context.Context) (latest *heimdall.Checkpoint, ok bool, err error) + SynchronizeMilestones(ctx context.Context) (latest *heimdall.Milestone, ok bool, err error) SynchronizeSpans(ctx context.Context, blockNum uint64) error Ready(ctx context.Context) <-chan error } @@ -290,7 +299,7 @@ func (s *Sync) applyNewBlockOnTip(ctx context.Context, event EventNewBlock, ccb amount = 1024 } - opts := []p2p.FetcherOption{p2p.WithMaxRetries(0), p2p.WithResponseTimeout(time.Second)} + opts := []p2p.FetcherOption{p2p.WithMaxRetries(0), p2p.WithResponseTimeout(5 * time.Second)} blocks, err := s.p2pService.FetchBlocksBackwardsByHash(ctx, newBlockHeaderHash, amount, event.PeerId, opts...) if err != nil { if s.ignoreFetchBlocksErrOnTipEvent(err) { @@ -331,12 +340,13 @@ func (s *Sync) applyNewBlockOnTip(ctx context.Context, event EventNewBlock, ccb oldTip := ccb.Tip() newConnectedHeaders, err := ccb.Connect(ctx, headerChain) if err != nil { + // IMPORTANT: we just log the error and do not return + // to process the possibility of a partially connected header chain s.logger.Debug( - syncLogPrefix("applyNewBlockOnTip: couldn't connect a header to the local chain tip, ignoring"), + syncLogPrefix("applyNewBlockOnTip: couldn't connect header chain to the local chain tip"), + "partiallyConnected", len(newConnectedHeaders), "err", err, ) - - return nil } if len(newConnectedHeaders) == 0 { return nil @@ -370,8 +380,9 @@ func (s *Sync) applyNewBlockOnTip(ctx context.Context, event EventNewBlock, ccb } } - // len(newConnectedHeaders) is always <= len(blockChain) - newConnectedBlocks := blockChain[len(blockChain)-len(newConnectedHeaders):] + newBlocksStartIdx := firstNewConnectedHeader.Number.Uint64() - blockChain[0].NumberU64() + newBlocksEndIdx := newBlocksStartIdx + uint64(len(newConnectedHeaders)) + newConnectedBlocks := blockChain[newBlocksStartIdx:newBlocksEndIdx] if len(newConnectedBlocks) > 1 { s.logger.Info( syncLogPrefix("inserting multiple connected blocks"), @@ -384,6 +395,11 @@ func (s *Sync) applyNewBlockOnTip(ctx context.Context, event EventNewBlock, ccb return err } + if event.Source == EventSourceBlockProducer { + go s.publishNewBlock(ctx, event.NewBlock) + go s.p2pService.PublishNewBlockHashes(event.NewBlock) + } + if event.Source == EventSourceP2PNewBlock { // https://github.com/ethereum/devp2p/blob/master/caps/eth.md#block-propagation // devp2p spec: when a NewBlock announcement message is received from a peer, the client first verifies the @@ -760,10 +776,19 @@ func (s *Sync) Run(ctx context.Context) error { // canonical chain tip. func (s *Sync) initialiseCcb(ctx context.Context, result syncToTipResult) (*CanonicalChainBuilder, error) { tip := result.latestTip + tipNum := tip.Number.Uint64() - rootNum := result.latestWaypoint.EndBlock().Uint64() - if rootNum > tipNum { - return nil, fmt.Errorf("unexpected rootNum > tipNum: %d > %d", rootNum, tipNum) + rootNum := uint64(0) + + if tipNum > maxFinalizationHeight { + rootNum = tipNum - maxFinalizationHeight + } + + if result.latestWaypoint != nil { + rootNum = result.latestWaypoint.EndBlock().Uint64() + if result.latestWaypoint.EndBlock().Uint64() > tipNum { + return nil, fmt.Errorf("unexpected rootNum > tipNum: %d > %d", rootNum, tipNum) + } } s.logger.Debug(syncLogPrefix("initialising canonical chain builder"), "rootNum", rootNum, "tipNum", tipNum) @@ -801,40 +826,51 @@ type syncToTipResult struct { } func (s *Sync) syncToTip(ctx context.Context) (syncToTipResult, error) { - startTime := time.Now() latestTipOnStart, err := s.execution.CurrentHeader(ctx) if err != nil { return syncToTipResult{}, err } - result, err := s.syncToTipUsingCheckpoints(ctx, latestTipOnStart) + finalisedTip := syncToTipResult{ + latestTip: latestTipOnStart, + } + + startTime := time.Now() + result, ok, err := s.syncToTipUsingCheckpoints(ctx, finalisedTip.latestTip) if err != nil { return syncToTipResult{}, err } - blocks := result.latestTip.Number.Uint64() - latestTipOnStart.Number.Uint64() - s.logger.Info( - syncLogPrefix("checkpoint sync finished"), - "tip", result.latestTip.Number.Uint64(), - "time", common.PrettyAge(startTime), - "blocks", blocks, - "blk/sec", uint64(float64(blocks)/time.Since(startTime).Seconds()), - ) + if ok { + blocks := result.latestTip.Number.Uint64() - finalisedTip.latestTip.Number.Uint64() + s.logger.Info( + syncLogPrefix("checkpoint sync finished"), + "tip", result.latestTip.Number.Uint64(), + "time", common.PrettyAge(startTime), + "blocks", blocks, + "blk/sec", uint64(float64(blocks)/time.Since(startTime).Seconds()), + ) + + finalisedTip = result + } startTime = time.Now() - result, err = s.syncToTipUsingMilestones(ctx, result.latestTip) + result, ok, err = s.syncToTipUsingMilestones(ctx, finalisedTip.latestTip) if err != nil { return syncToTipResult{}, err } - blocks = result.latestTip.Number.Uint64() - latestTipOnStart.Number.Uint64() - s.logger.Info( - syncLogPrefix("sync to tip finished"), - "tip", result.latestTip.Number.Uint64(), - "time", common.PrettyAge(startTime), - "blocks", blocks, - "blk/sec", uint64(float64(blocks)/time.Since(startTime).Seconds()), - ) + if ok { + blocks := result.latestTip.Number.Uint64() - finalisedTip.latestTip.Number.Uint64() + s.logger.Info( + syncLogPrefix("milestone sync finished"), + "tip", result.latestTip.Number.Uint64(), + "time", common.PrettyAge(startTime), + "blocks", blocks, + "blk/sec", uint64(float64(blocks)/time.Since(startTime).Seconds()), + ) + finalisedTip = result + } if result.latestTip != nil { if err := s.heimdallSync.SynchronizeSpans(ctx, result.latestTip.Number.Uint64()); err != nil { @@ -842,24 +878,24 @@ func (s *Sync) syncToTip(ctx context.Context) (syncToTipResult, error) { } } - return result, nil + return finalisedTip, nil } -func (s *Sync) syncToTipUsingCheckpoints(ctx context.Context, tip *types.Header) (syncToTipResult, error) { - syncCheckpoints := func(ctx context.Context) (heimdall.Waypoint, error) { +func (s *Sync) syncToTipUsingCheckpoints(ctx context.Context, tip *types.Header) (syncToTipResult, bool, error) { + syncCheckpoints := func(ctx context.Context) (heimdall.Waypoint, bool, error) { return s.heimdallSync.SynchronizeCheckpoints(ctx) } return s.sync(ctx, tip, syncCheckpoints, s.blockDownloader.DownloadBlocksUsingCheckpoints) } -func (s *Sync) syncToTipUsingMilestones(ctx context.Context, tip *types.Header) (syncToTipResult, error) { - syncMilestones := func(ctx context.Context) (heimdall.Waypoint, error) { +func (s *Sync) syncToTipUsingMilestones(ctx context.Context, tip *types.Header) (syncToTipResult, bool, error) { + syncMilestones := func(ctx context.Context) (heimdall.Waypoint, bool, error) { return s.heimdallSync.SynchronizeMilestones(ctx) } return s.sync(ctx, tip, syncMilestones, s.blockDownloader.DownloadBlocksUsingMilestones) } -type waypointSyncFunc func(ctx context.Context) (heimdall.Waypoint, error) +type waypointSyncFunc func(ctx context.Context) (heimdall.Waypoint, bool, error) type blockDownloadFunc func(ctx context.Context, startBlockNum uint64, endBlockNum *uint64) (*types.Header, error) func (s *Sync) sync( @@ -867,9 +903,10 @@ func (s *Sync) sync( tip *types.Header, waypointSync waypointSyncFunc, blockDownload blockDownloadFunc, -) (syncToTipResult, error) { +) (syncToTipResult, bool, error) { var waypoint heimdall.Waypoint var err error + var ok bool var syncTo *uint64 @@ -878,9 +915,12 @@ func (s *Sync) sync( } for { - waypoint, err = waypointSync(ctx) + waypoint, ok, err = waypointSync(ctx) if err != nil { - return syncToTipResult{}, err + return syncToTipResult{}, false, err + } + if !ok { + return syncToTipResult{}, false, nil } // notify about latest waypoint end block so that eth_syncing API doesn't flicker on initial sync @@ -888,7 +928,7 @@ func (s *Sync) sync( newTip, err := blockDownload(ctx, tip.Number.Uint64()+1, syncTo) if err != nil { - return syncToTipResult{}, err + return syncToTipResult{}, false, err } if newTip == nil { @@ -906,7 +946,7 @@ func (s *Sync) sync( // note: if we face a failure during execution of finalized waypoints blocks, it means that // we're wrong and the blocks are not considered as bad blocks, so we should terminate err = s.handleWaypointExecutionErr(ctx, tip, err) - return syncToTipResult{}, err + return syncToTipResult{}, false, err } } @@ -919,7 +959,7 @@ func (s *Sync) sync( } } - return syncToTipResult{latestTip: tip, latestWaypoint: waypoint}, nil + return syncToTipResult{latestTip: tip, latestWaypoint: waypoint}, true, nil } func (s *Sync) handleWaypointExecutionErr(ctx context.Context, lastCorrectTip *types.Header, execErr error) error { diff --git a/polygon/sync/tip_events.go b/polygon/sync/tip_events.go index c9175a518fb..d5016ee825b 100644 --- a/polygon/sync/tip_events.go +++ b/polygon/sync/tip_events.go @@ -42,6 +42,7 @@ type EventSource string const EventSourceP2PNewBlockHashes EventSource = "p2p-new-block-hashes-source" const EventSourceP2PNewBlock EventSource = "p2p-new-block-source" +const EventSourceBlockProducer EventSource = "mined-block-producer" type EventTopic string @@ -114,25 +115,31 @@ type heimdallObserverRegistrar interface { RegisterMilestoneObserver(callback func(*heimdall.Milestone), opts ...heimdall.ObserverOption) event.UnregisterFunc } -func NewTipEvents(logger log.Logger, p2pReg p2pObserverRegistrar, heimdallReg heimdallObserverRegistrar) *TipEvents { +type MinedBlockObserverRegistrar interface { + RegisterMinedBlockObserver(callback func(*types.Block)) event.UnregisterFunc +} + +func NewTipEvents(logger log.Logger, p2pReg p2pObserverRegistrar, heimdallReg heimdallObserverRegistrar, minedBlockReg MinedBlockObserverRegistrar) *TipEvents { heimdallEventsChannel := NewEventChannel[Event](10, WithEventChannelLogging(logger, log.LvlTrace, EventTopicHeimdall.String())) p2pEventsChannel := NewEventChannel[Event](1000, WithEventChannelLogging(logger, log.LvlTrace, EventTopicP2P.String())) compositeEventsChannel := NewTipEventsCompositeChannel(heimdallEventsChannel, p2pEventsChannel) return &TipEvents{ - logger: logger, - events: compositeEventsChannel, - p2pObserverRegistrar: p2pReg, - heimdallObserverRegistrar: heimdallReg, - blockEventsSpamGuard: newBlockEventsSpamGuard(logger), + logger: logger, + events: compositeEventsChannel, + p2pObserverRegistrar: p2pReg, + heimdallObserverRegistrar: heimdallReg, + minedBlockObserverRegistrar: minedBlockReg, + blockEventsSpamGuard: newBlockEventsSpamGuard(logger), } } type TipEvents struct { - logger log.Logger - events *TipEventsCompositeChannel - p2pObserverRegistrar p2pObserverRegistrar - heimdallObserverRegistrar heimdallObserverRegistrar - blockEventsSpamGuard blockEventsSpamGuard + logger log.Logger + events *TipEventsCompositeChannel + p2pObserverRegistrar p2pObserverRegistrar + heimdallObserverRegistrar heimdallObserverRegistrar + minedBlockObserverRegistrar MinedBlockObserverRegistrar + blockEventsSpamGuard blockEventsSpamGuard } func (te *TipEvents) Events() <-chan Event { @@ -142,6 +149,23 @@ func (te *TipEvents) Events() <-chan Event { func (te *TipEvents) Run(ctx context.Context) error { te.logger.Info(syncLogPrefix("running tip events component")) + newMinedBlockObserverCancel := te.minedBlockObserverRegistrar.RegisterMinedBlockObserver(func(msg *types.Block) { + te.logger.Trace( + "[tip-events] mined block event received from block producer", + "hash", msg.Hash(), + "number", msg.NumberU64(), + ) + + te.events.PushEvent(Event{ + Type: EventTypeNewBlock, + newBlock: EventNewBlock{ + NewBlock: msg, + Source: EventSourceBlockProducer, + }, + }) + }) + defer newMinedBlockObserverCancel() + newBlockObserverCancel := te.p2pObserverRegistrar.RegisterNewBlockObserver(func(message *p2p.DecodedInboundMessage[*eth.NewBlockPacket]) { block := message.Decoded.Block diff --git a/tests/bor/helper/miner.go b/tests/bor/helper/miner.go index 46d8d2c3f8a..a872138f1dd 100644 --- a/tests/bor/helper/miner.go +++ b/tests/bor/helper/miner.go @@ -136,7 +136,7 @@ func InitMiner(ctx context.Context, dirName string, genesis *types.Genesis, priv GPO: ethconfig.Defaults.GPO, Miner: params.MiningConfig{ Etherbase: crypto.PubkeyToAddress(privKey.PublicKey), - GasLimit: genesis.GasLimit, + GasLimit: &genesis.GasLimit, GasPrice: big.NewInt(1), Recommit: 125 * time.Second, SigKey: privKey, diff --git a/turbo/app/snapshots_cmd.go b/turbo/app/snapshots_cmd.go index d37d714ce86..026e9badaf2 100644 --- a/turbo/app/snapshots_cmd.go +++ b/turbo/app/snapshots_cmd.go @@ -225,11 +225,19 @@ var snapshotCommand = cli.Command{ Name: "rm-all-state-snapshots", Action: func(cliCtx *cli.Context) error { dirs := datadir.New(cliCtx.String(utils.DataDirFlag.Name)) - //os.Remove(filepath.Join(dirs.Snap, "salt-state.txt")) return dir.DeleteFiles(dirs.SnapIdx, dirs.SnapHistory, dirs.SnapDomain, dirs.SnapAccessors) }, Flags: joinFlags([]cli.Flag{&utils.DataDirFlag}), }, + { + Name: "reset-to-old-ver-format", + Usage: "change all the snapshots to 3.0 file format", + Action: func(cliCtx *cli.Context) error { + dirs := datadir.New(cliCtx.String(utils.DataDirFlag.Name)) + return dirs.RenameNewVersions() + }, + Flags: joinFlags([]cli.Flag{&utils.DataDirFlag}), + }, { Name: "rm-state-snapshots", Aliases: []string{"rm-state-segments", "rm-state"}, @@ -540,20 +548,12 @@ func doRmStateSnapshots(cliCtx *cli.Context) error { } var removed uint64 - var cleanedSize datasize.ByteSize for _, res := range toRemove { - s, err := os.Stat(res.Path) - if err != nil { - return fmt.Errorf("failed to stat %s: %w", res.Path, err) - } - cleanedSize += datasize.ByteSize(s.Size()) - - if err := os.Remove(res.Path); err != nil { - return fmt.Errorf("failed to remove %s: %w", res.Path, err) - } + os.Remove(res.Path) + os.Remove(res.Path + ".torrent") removed++ } - fmt.Printf("removed %d (%v) state snapshot segments files\n", removed, cleanedSize.HumanReadable()) + fmt.Printf("removed %d state snapshot segments files\n", removed) return nil } @@ -699,6 +699,10 @@ func doIntegrity(cliCtx *cli.Context) error { if err := blockReader.(*freezeblocks.BlockReader).IntegrityTxnID(failFast); err != nil { return err } + case integrity.HeaderNoGaps: + if err := integrity.NoGapsInCanonicalHeaders(ctx, db, blockReader, failFast); err != nil { + return err + } case integrity.Blocks: if err := integrity.SnapBlocksRead(ctx, db, blockReader, 0, 0, failFast); err != nil { return err @@ -1049,8 +1053,10 @@ func doClearIndexing(cliCtx *cli.Context) error { } // remove salt-state.txt and salt-blocks.txt - // os.Remove(filepath.Join(snapDir, "salt-state.txt")) - // os.Remove(filepath.Join(snapDir, "salt-blocks.txt")) + //os.Remove(filepath.Join(snapDir, "salt-state.txt")) + //os.Remove(filepath.Join(snapDir, "salt-state.txt.torrent")) + //os.Remove(filepath.Join(snapDir, "salt-blocks.txt")) + //os.Remove(filepath.Join(snapDir, "salt-blocks.txt.torrent")) return nil } @@ -1358,7 +1364,7 @@ func openSnaps(ctx context.Context, cfg ethconfig.BlocksFreezing, dirs datadir.D } defer tx.Rollback() stats.LogStats(tx, logger, func(endTxNumMinimax uint64) (uint64, error) { - _, histBlockNumProgress, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, blockReader)).FindBlockNum(tx, endTxNumMinimax) + histBlockNumProgress, _, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)).FindBlockNum(tx, endTxNumMinimax) return histBlockNumProgress, err }) @@ -1789,7 +1795,7 @@ func doRetireCommand(cliCtx *cli.Context, dirs datadir.Dirs) error { return err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, blockReader)) var lastTxNum uint64 if err := db.Update(ctx, func(tx kv.RwTx) error { execProgress, _ := stages.GetStageProgress(tx, stages.Execution) diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go index eceeea39a02..82da5386279 100644 --- a/turbo/cli/default_flags.go +++ b/turbo/cli/default_flags.go @@ -155,6 +155,7 @@ var DefaultFlags = []cli.Flag{ &utils.MinerNotifyFlag, &utils.MinerGasLimitFlag, &utils.MinerEtherbaseFlag, + &utils.MinerGasPriceFlag, &utils.MinerExtraDataFlag, &utils.MinerNoVerfiyFlag, &utils.MinerSigningKeyFileFlag, diff --git a/turbo/engineapi/engine_server.go b/turbo/engineapi/engine_server.go index 16e58df9f9c..99e8b38f5ce 100644 --- a/turbo/engineapi/engine_server.go +++ b/turbo/engineapi/engine_server.go @@ -629,8 +629,9 @@ func (s *EngineServer) forkchoiceUpdated(ctx context.Context, forkchoiceState *e } var resp *execution.AssembleBlockResponse - - execBusy, err := waitForStuff(500*time.Millisecond, func() (bool, error) { + // Wait for the execution service to be ready to assemble a block. Wait a full slot duration (12 seconds) to ensure that the execution service is not busy. + // Blocks are important and 0.5 seconds is not enough to wait for the execution service to be ready. + execBusy, err := waitForStuff(time.Duration(s.config.SecondsPerSlot())*time.Second, func() (bool, error) { resp, err = s.executionService.AssembleBlock(ctx, req) if err != nil { return false, err diff --git a/turbo/execution/eth1/forkchoice.go b/turbo/execution/eth1/forkchoice.go index 6611b045fea..6077d497330 100644 --- a/turbo/execution/eth1/forkchoice.go +++ b/turbo/execution/eth1/forkchoice.go @@ -43,7 +43,7 @@ import ( // This is the range in which we sanity check and potentially fix the canonical chain if it is broken. // a broken canonical chain is very dangerous, as it can lead to a situation where the RPC and snapshots break down. // better to have an hack than to regenerate all chains. -const fixCanonicalFailsafeRange = 512 +const fixCanonicalFailsafeRange = 16 const startPruneFrom = 1024 diff --git a/turbo/jsonrpc/debug_api.go b/turbo/jsonrpc/debug_api.go index c4fb35b542f..8e5156f03d4 100644 --- a/turbo/jsonrpc/debug_api.go +++ b/turbo/jsonrpc/debug_api.go @@ -99,7 +99,7 @@ func (api *PrivateDebugAPIImpl) StorageRangeAt(ctx context.Context, blockHash co } defer tx.Rollback() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) number, err := api._blockReader.HeaderNumber(ctx, tx, blockHash) if err != nil { return StorageRangeResult{}, err @@ -211,7 +211,7 @@ func (api *PrivateDebugAPIImpl) AccountRange(ctx context.Context, blockNrOrHash } } - dumper := state.NewDumper(tx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)), blockNumber) + dumper := state.NewDumper(tx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)), blockNumber) res, err := dumper.IteratorDump(excludeCode, excludeStorage, common.BytesToAddress(startKey), maxResults) if err != nil { return state.IteratorDump{}, err @@ -242,7 +242,7 @@ func (api *PrivateDebugAPIImpl) GetModifiedAccountsByNumber(ctx context.Context, return nil, err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) // forces negative numbers to fail (too large) but allows zero startNum := uint64(startNumber.Int64()) @@ -309,7 +309,7 @@ func (api *PrivateDebugAPIImpl) GetModifiedAccountsByHash(ctx context.Context, s } defer tx.Rollback() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) startBlock, err := api.blockByHashWithSenders(ctx, tx, startHash) if err != nil { @@ -355,7 +355,7 @@ func (api *PrivateDebugAPIImpl) AccountAt(ctx context.Context, blockHash common. } defer tx.Rollback() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) number, err := api._blockReader.HeaderNumber(ctx, tx, blockHash) if err != nil { @@ -560,7 +560,7 @@ func (api *PrivateDebugAPIImpl) GetRawTransaction(ctx context.Context, txnHash c return nil, err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) // Private API returns 0 if transaction is not found. if blockNum == 0 && chainConfig.Bor != nil { diff --git a/turbo/jsonrpc/erigon_block.go b/turbo/jsonrpc/erigon_block.go index e5523ad3787..9bac443bae3 100644 --- a/turbo/jsonrpc/erigon_block.go +++ b/turbo/jsonrpc/erigon_block.go @@ -219,7 +219,7 @@ func (api *ErigonImpl) GetBalanceChangesInBlock(ctx context.Context, blockNrOrHa } defer tx.Rollback() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) balancesMapping := make(map[common.Address]*hexutil.Big) latestState, err := rpchelper.CreateStateReader(ctx, tx, api._blockReader, blockNrOrHash, 0, api.filters, api.stateCache, "") if err != nil { diff --git a/turbo/jsonrpc/erigon_receipts.go b/turbo/jsonrpc/erigon_receipts.go index 4fac7181288..17322c15667 100644 --- a/turbo/jsonrpc/erigon_receipts.go +++ b/turbo/jsonrpc/erigon_receipts.go @@ -192,7 +192,7 @@ func (api *ErigonImpl) GetLatestLogs(ctx context.Context, crit filters.FilterCri exec := exec3.NewTraceWorker(tx, chainConfig, api.engine(), api._blockReader, nil) defer exec.Close() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) txNumbers, err := applyFiltersV3(txNumsReader, tx, begin, end, crit) if err != nil { return erigonLogs, err diff --git a/turbo/jsonrpc/eth_accounts.go b/turbo/jsonrpc/eth_accounts.go index 5fbeaf86251..7caeb5f0e7e 100644 --- a/turbo/jsonrpc/eth_accounts.go +++ b/turbo/jsonrpc/eth_accounts.go @@ -108,7 +108,7 @@ func (api *APIImpl) GetCode(ctx context.Context, address libcommon.Address, bloc } acc, err := reader.ReadAccountData(address) - if acc == nil || err != nil { + if acc == nil || err != nil || acc.IsEmptyCodeHash() { return hexutility.Bytes(""), nil } res, _ := reader.ReadAccountCode(address, acc.Incarnation) diff --git a/turbo/jsonrpc/eth_block.go b/turbo/jsonrpc/eth_block.go index 968fc7fa5c8..f5c4929e9f5 100644 --- a/turbo/jsonrpc/eth_block.go +++ b/turbo/jsonrpc/eth_block.go @@ -91,7 +91,7 @@ func (api *APIImpl) CallBundle(ctx context.Context, txHashes []common.Hash, stat } defer func(start time.Time) { log.Trace("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) stateBlockNumber, hash, latest, err := rpchelper.GetBlockNumber(ctx, stateBlockNumberOrHash, tx, api._blockReader, api.filters) if err != nil { return nil, err diff --git a/turbo/jsonrpc/eth_call.go b/turbo/jsonrpc/eth_call.go index 0112f509c61..6c60ab21e6c 100644 --- a/turbo/jsonrpc/eth_call.go +++ b/turbo/jsonrpc/eth_call.go @@ -88,7 +88,7 @@ func (api *APIImpl) Call(ctx context.Context, args ethapi2.CallArgs, blockNrOrHa return nil, err } if block == nil { - return nil, nil + return nil, errors.New("block not found") } stateReader, err := rpchelper.CreateStateReader(ctx, tx, api._blockReader, blockNrOrHash, 0, api.filters, api.stateCache, chainConfig.ChainName) @@ -166,8 +166,9 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs return 0, err } - // try and get the block from the lru cache first then try DB before failing block := api.tryBlockFromLru(blockHash) + + // try and get the block from the lru cache first then try DB before failing if block == nil { block, err = api.blockWithSenders(ctx, dbtx, blockHash, blockNum) if err != nil { @@ -175,12 +176,20 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs } } + // try to check if it is a pending block + if block == nil { + b := api.filters.LastPendingBlock() + if b != nil && blockNum == b.NumberU64() { + block = b + } + } + if block == nil { return 0, errors.New(fmt.Sprintf("could not find the block %s in cache or db", blockNrOrHash.String())) } header := block.HeaderNoCopy() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) stateReader, err := rpchelper.CreateStateReaderFromBlockNumber(ctx, dbtx, txNumsReader, blockNum, isLatest, 0, api.stateCache, chainConfig.ChainName) if err != nil { return 0, err @@ -351,7 +360,7 @@ func (api *APIImpl) getProof(ctx context.Context, roTx kv.Tx, address libcommon. } if blockNrOrHash.BlockNumber.Uint64() < latestBlock { // Get first txnum of blockNumber+1 to ensure that correct state root will be restored as of blockNumber has been executed - lastTxnInBlock, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)).Min(tx, blockNrOrHash.BlockNumber.Uint64()+1) + lastTxnInBlock, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)).Min(tx, blockNrOrHash.BlockNumber.Uint64()+1) if err != nil { return nil, err } @@ -748,7 +757,7 @@ func (api *APIImpl) CreateAccessList(ctx context.Context, args ethapi2.CallArgs, return nil, nil } var stateReader state.StateReader - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) if latest { cacheView, err := api.stateCache.View(ctx, tx) diff --git a/turbo/jsonrpc/eth_receipts.go b/turbo/jsonrpc/eth_receipts.go index 7bce71d6387..b65dbc11f36 100644 --- a/turbo/jsonrpc/eth_receipts.go +++ b/turbo/jsonrpc/eth_receipts.go @@ -56,7 +56,7 @@ func (api *BaseAPI) getReceipt(ctx context.Context, cc *chain.Config, tx kv.Temp } func (api *BaseAPI) getReceiptsGasUsed(ctx context.Context, tx kv.TemporalTx, block *types.Block) (types.Receipts, error) { - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) return api.receiptsGenerator.GetReceiptsGasUsed(tx, block, txNumsReader) } @@ -221,7 +221,7 @@ func (api *BaseAPI) getLogsV3(ctx context.Context, tx kv.TemporalTx, begin, end var header *types.Header - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) txNumbers, err := applyFiltersV3(txNumsReader, tx, begin, end, crit) if err != nil { return logs, err @@ -451,7 +451,7 @@ func (api *APIImpl) GetTransactionReceipt(ctx context.Context, txnHash common.Ha return nil, nil } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) txNumMin, err := txNumsReader.Min(tx, blockNum) if err != nil { @@ -627,7 +627,7 @@ func (i *MapTxNum2BlockNumIter) Next() (txNum, blockNum uint64, txIndex int, isF blockNumChanged = true var ok bool - ok, i.blockNum, err = i.txNumsReader.FindBlockNum(i.tx, txNum) + i.blockNum, ok, err = i.txNumsReader.FindBlockNum(i.tx, txNum) if err != nil { return } diff --git a/turbo/jsonrpc/eth_subscribe_test.go b/turbo/jsonrpc/eth_subscribe_test.go index 0aad2fdf209..9c30ff25dc2 100644 --- a/turbo/jsonrpc/eth_subscribe_test.go +++ b/turbo/jsonrpc/eth_subscribe_test.go @@ -18,7 +18,7 @@ package jsonrpc import ( "context" - "fmt" + "sync" "testing" "github.com/stretchr/testify/require" @@ -58,14 +58,23 @@ func TestEthSubscribe(t *testing.T) { for _, err = range m.Send(&sentry.InboundMessage{Id: sentry.MessageId_BLOCK_HEADERS_66, Data: b, PeerId: m.PeerId}) { require.NoError(err) } - m.ReceiveWg.Wait() // Wait for all messages to be processed before we proceeed + m.ReceiveWg.Wait() // Wait for all messages to be processed before we proceed ctx := context.Background() logger := log.New() backendServer := privateapi.NewEthBackendServer(ctx, nil, m.DB, m.Notifications, m.BlockReader, logger, builder.NewLatestBlockBuiltStore()) backendClient := direct.NewEthBackendClientDirect(backendServer) backend := rpcservices.NewRemoteBackend(backendClient, m.DB, m.BlockReader) - ff := rpchelper.New(ctx, rpchelper.DefaultFiltersConfig, backend, nil, nil, func() {}, m.Log) + // Creating a new filter will set up new internal subscription channels actively managed by subscription tasks. + // We must wait for the first NEW_SNAPSHOT notification, which is always sent unconditionally by EthBackendServer + // at the start of Subscribe, to be sure that the subscription is ready, otherwise we could miss some events. + subscriptionReadyWg := sync.WaitGroup{} + subscriptionReadyWg.Add(1) + onNewSnapshot := func() { + subscriptionReadyWg.Done() + } + ff := rpchelper.New(ctx, rpchelper.DefaultFiltersConfig, backend, nil, nil, onNewSnapshot, m.Log) + subscriptionReadyWg.Wait() // This is needed *before* stages.StageLoopIteration, which sends NEW_HEADER events newHeads, id := ff.SubscribeNewHeads(16) defer ff.UnsubscribeHeads(id) @@ -80,7 +89,6 @@ func TestEthSubscribe(t *testing.T) { for i := uint64(1); i <= highestSeenHeader; i++ { header := <-newHeads - fmt.Printf("Got header %d\n", header.Number.Uint64()) require.Equal(i, header.Number.Uint64()) } } diff --git a/turbo/jsonrpc/eth_txs.go b/turbo/jsonrpc/eth_txs.go index 4af8e7cc076..ebe3b329b0b 100644 --- a/turbo/jsonrpc/eth_txs.go +++ b/turbo/jsonrpc/eth_txs.go @@ -56,7 +56,7 @@ func (api *APIImpl) GetTransactionByHash(ctx context.Context, txnHash common.Has return nil, err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) // Private API returns 0 if transaction is not found. if blockNum == 0 && chainConfig.Bor != nil { diff --git a/turbo/jsonrpc/otterscan_api.go b/turbo/jsonrpc/otterscan_api.go index 4ab6bccc314..e4fcad1a491 100644 --- a/turbo/jsonrpc/otterscan_api.go +++ b/turbo/jsonrpc/otterscan_api.go @@ -144,7 +144,7 @@ func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.TemporalTx, ha } engine := api.engine() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) ibs, blockCtx, _, rules, signer, err := transactions.ComputeBlockContext(ctx, engine, block.HeaderNoCopy(), chainConfig, api._blockReader, txNumsReader, tx, int(txIndex)) if err != nil { return nil, err diff --git a/turbo/jsonrpc/otterscan_contract_creator.go b/turbo/jsonrpc/otterscan_contract_creator.go index 47af6bedc59..c290edb0e26 100644 --- a/turbo/jsonrpc/otterscan_contract_creator.go +++ b/turbo/jsonrpc/otterscan_contract_creator.go @@ -44,7 +44,7 @@ func (api *OtterscanAPIImpl) GetContractCreator(ctx context.Context, addr common return nil, err } defer tx.Rollback() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) latestState := rpchelper.NewLatestStateReader(tx) plainStateAcc, err := latestState.ReadAccountData(addr) @@ -165,7 +165,7 @@ func (api *OtterscanAPIImpl) GetContractCreator(ctx context.Context, addr common return nil, fmt.Errorf("binary search between %d-%d doesn't find anything", nextTxnID, prevTxnID) } - ok, bn, err := txNumsReader.FindBlockNum(tx, creationTxnID) + bn, ok, err := txNumsReader.FindBlockNum(tx, creationTxnID) if err != nil { return nil, err } diff --git a/turbo/jsonrpc/otterscan_has_code.go b/turbo/jsonrpc/otterscan_has_code.go index c174bb0011c..bd307a7cb88 100644 --- a/turbo/jsonrpc/otterscan_has_code.go +++ b/turbo/jsonrpc/otterscan_has_code.go @@ -44,7 +44,7 @@ func (api *OtterscanAPIImpl) HasCode(ctx context.Context, address common.Address return false, err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) reader, err := rpchelper.CreateHistoryStateReader(tx, txNumsReader, blockNumber, 0, chainConfig.ChainName) if err != nil { return false, err diff --git a/turbo/jsonrpc/otterscan_search_trace.go b/turbo/jsonrpc/otterscan_search_trace.go index be1ade8a4ee..3fc9b613e63 100644 --- a/turbo/jsonrpc/otterscan_search_trace.go +++ b/turbo/jsonrpc/otterscan_search_trace.go @@ -75,7 +75,7 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.TemporalTx, ctx context.Context, return false, nil, nil } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) reader, err := rpchelper.CreateHistoryStateReader(dbtx, txNumsReader, blockNum, 0, chainConfig.ChainName) if err != nil { return false, nil, err diff --git a/turbo/jsonrpc/otterscan_search_v3.go b/turbo/jsonrpc/otterscan_search_v3.go index e9a1ce7919e..0edf59627b1 100644 --- a/turbo/jsonrpc/otterscan_search_v3.go +++ b/turbo/jsonrpc/otterscan_search_v3.go @@ -138,7 +138,7 @@ func (api *OtterscanAPIImpl) searchTransactionsBeforeV3(tx kv.TemporalTx, ctx co // Internal search code considers blockNum [including], so adjust the value fromBlockNum-- } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) fromTxNum := -1 if fromBlockNum != 0 { @@ -177,7 +177,7 @@ func createForwardTxNumIter(tx kv.TemporalTx, txNumsReader rawdbv3.TxNumsReader, func (api *OtterscanAPIImpl) searchTransactionsAfterV3(tx kv.TemporalTx, ctx context.Context, addr common.Address, fromBlockNum uint64, pageSize uint16) (*TransactionsWithReceipts, error) { isLastPage := false fromTxNum := -1 - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) if fromBlockNum == 0 { isLastPage = true diff --git a/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go b/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go index e083faa6a07..b111b2fda42 100644 --- a/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go +++ b/turbo/jsonrpc/otterscan_transaction_by_sender_and_nonce.go @@ -125,7 +125,7 @@ func (api *OtterscanAPIImpl) GetTransactionBySenderAndNonce(ctx context.Context, } return true }) - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) if searchErr != nil { return nil, searchErr @@ -133,7 +133,7 @@ func (api *OtterscanAPIImpl) GetTransactionBySenderAndNonce(ctx context.Context, if creationTxnID == 0 { return nil, nil } - ok, bn, err := txNumsReader.FindBlockNum(tx, creationTxnID) + bn, ok, err := txNumsReader.FindBlockNum(tx, creationTxnID) if err != nil { return nil, err } diff --git a/turbo/jsonrpc/parity_api.go b/turbo/jsonrpc/parity_api.go index cf9904a7f18..1d0ff63dc81 100644 --- a/turbo/jsonrpc/parity_api.go +++ b/turbo/jsonrpc/parity_api.go @@ -74,7 +74,7 @@ func (api *ParityAPIImpl) ListStorageKeys(ctx context.Context, account libcommon } else if a == nil { return nil, errors.New("acc not found") } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) bn := rawdb.ReadCurrentBlockNumber(tx) minTxNum, err := txNumsReader.Min(tx, *bn) diff --git a/turbo/jsonrpc/receipts/bor_receipts_generator.go b/turbo/jsonrpc/receipts/bor_receipts_generator.go index 68656b2d718..3bc92e903e0 100644 --- a/turbo/jsonrpc/receipts/bor_receipts_generator.go +++ b/turbo/jsonrpc/receipts/bor_receipts_generator.go @@ -50,7 +50,7 @@ func (g *BorGenerator) GenerateBorReceipt(ctx context.Context, tx kv.TemporalTx, return receipt, nil } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, g.blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, g.blockReader)) ibs, blockContext, _, _, _, err := transactions.ComputeBlockContext(ctx, g.engine, block.HeaderNoCopy(), chainConfig, g.blockReader, txNumsReader, tx, len(block.Transactions())) // we want to get the state at the end of the block if err != nil { return nil, err diff --git a/turbo/jsonrpc/receipts/receipts_generator.go b/turbo/jsonrpc/receipts/receipts_generator.go index 850d8aed191..b1bd3032af7 100644 --- a/turbo/jsonrpc/receipts/receipts_generator.go +++ b/turbo/jsonrpc/receipts/receipts_generator.go @@ -71,7 +71,7 @@ func NewGenerator(blockReader services.FullBlockReader, engine consensus.EngineR panic(err) } - txNumReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(context.Background(), blockReader)) + txNumReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(context.Background(), blockReader)) return &Generator{ receiptsCache: receiptsCache, @@ -104,7 +104,7 @@ func (g *Generator) GetCachedReceipt(ctx context.Context, hash common.Hash) (*ty } func (g *Generator) PrepareEnv(ctx context.Context, header *types.Header, cfg *chain.Config, tx kv.TemporalTx, txIndex int) (*ReceiptEnv, error) { - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, g.blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, g.blockReader)) ibs, _, _, _, _, err := transactions.ComputeBlockContext(ctx, g.engine, header, cfg, g.blockReader, txNumsReader, tx, txIndex) if err != nil { return nil, fmt.Errorf("ReceiptsGen: PrepareEnv: bn=%d, %w", header.Number.Uint64(), err) @@ -172,7 +172,10 @@ func (g *Generator) GetReceipt(ctx context.Context, cfg *chain.Config, tx kv.Tem mu := g.txnExecMutex.lock(txnHash) defer g.txnExecMutex.unlock(mu, txnHash) if receipt, ok := g.receiptCache.Get(txnHash); ok { - return receipt, nil + if receipt.BlockHash == blockHash { // elegant way to handle reorgs + return receipt, nil + } + g.receiptCache.Remove(txnHash) // remove old receipt with same hash, but different blockHash } var receipt *types.Receipt diff --git a/turbo/jsonrpc/trace_adhoc.go b/turbo/jsonrpc/trace_adhoc.go index 48b1662cb16..561cc879cfc 100644 --- a/turbo/jsonrpc/trace_adhoc.go +++ b/turbo/jsonrpc/trace_adhoc.go @@ -62,7 +62,7 @@ const ( TraceTypeVmTrace = "vmTrace" ) -// TraceCallParam (see SendTxArgs -- this allows optional prams plus don't use MixedcaseAddress +// TraceCallParam (see SendTxArgs -- this allows optional params plus don't use MixedcaseAddress type TraceCallParam struct { From *libcommon.Address `json:"from"` To *libcommon.Address `json:"to"` @@ -196,7 +196,7 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) } gasFeeCap, gasTipCap = gasPrice, gasPrice } else { - // User specified 1559 gas feilds (or none), use those + // User specified 1559 gas fields (or none), use those gasFeeCap = new(uint256.Int) if args.MaxFeePerGas != nil { overflow := gasFeeCap.SetFromBig(args.MaxFeePerGas.ToInt()) @@ -222,7 +222,7 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) } } if args.MaxFeePerBlobGas != nil { - maxFeePerBlobGas.SetFromBig(args.MaxFeePerBlobGas.ToInt()) + maxFeePerBlobGas = uint256.MustFromBig(args.MaxFeePerBlobGas.ToInt()) } } value := new(uint256.Int) @@ -824,7 +824,7 @@ func (api *TraceAPIImpl) ReplayTransaction(ctx context.Context, txHash libcommon return nil, err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) if !ok { if chainConfig.Bor == nil { diff --git a/turbo/jsonrpc/trace_filtering.go b/turbo/jsonrpc/trace_filtering.go index 6943bff11ec..e3c3e96e88b 100644 --- a/turbo/jsonrpc/trace_filtering.go +++ b/turbo/jsonrpc/trace_filtering.go @@ -66,7 +66,7 @@ func (api *TraceAPIImpl) Transaction(ctx context.Context, txHash common.Hash, ga return nil, err } - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) var isBorStateSyncTxn bool blockNumber, txNum, ok, err := api.txnLookup(ctx, tx, txHash) @@ -345,7 +345,7 @@ func (api *TraceAPIImpl) Filter(ctx context.Context, req TraceFilterRequest, gas func (api *TraceAPIImpl) filterV3(ctx context.Context, dbtx kv.TemporalTx, fromBlock, toBlock uint64, req TraceFilterRequest, stream *jsoniter.Stream, gasBailOut bool, traceConfig *config.TraceConfig) error { var fromTxNum, toTxNum uint64 var err error - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) if fromBlock > 0 { fromTxNum, err = txNumsReader.Min(dbtx, fromBlock) diff --git a/turbo/jsonrpc/tracing.go b/turbo/jsonrpc/tracing.go index 1bc5746bedd..6046d98b24d 100644 --- a/turbo/jsonrpc/tracing.go +++ b/turbo/jsonrpc/tracing.go @@ -104,7 +104,7 @@ func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rp } engine := api.engine() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) ibs, blockCtx, _, rules, signer, err := transactions.ComputeBlockContext(ctx, engine, block.HeaderNoCopy(), chainConfig, api._blockReader, txNumsReader, tx, 0) if err != nil { stream.WriteNil() @@ -321,7 +321,7 @@ func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash commo } engine := api.engine() - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) ibs, blockCtx, _, rules, signer, err := transactions.ComputeBlockContext(ctx, engine, block.HeaderNoCopy(), chainConfig, api._blockReader, txNumsReader, tx, txnIndex) if err != nil { stream.WriteNil() @@ -390,7 +390,7 @@ func (api *PrivateDebugAPIImpl) TraceCall(ctx context.Context, args ethapi.CallA if config == nil || config.TxIndex == nil || isLatest { stateReader, err = rpchelper.CreateStateReader(ctx, dbtx, api._blockReader, blockNrOrHash, 0, api.filters, api.stateCache, chainConfig.ChainName) } else { - txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, api._blockReader)) + txNumsReader := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, api._blockReader)) stateReader, err = rpchelper.CreateHistoryStateReader(dbtx, txNumsReader, blockNumber, int(*config.TxIndex), chainConfig.ChainName) } if err != nil { diff --git a/turbo/rpchelper/helper.go b/turbo/rpchelper/helper.go index e6b2b68e02c..ba7d587ca7d 100644 --- a/turbo/rpchelper/helper.go +++ b/turbo/rpchelper/helper.go @@ -162,7 +162,7 @@ func CreateStateReader(ctx context.Context, tx kv.TemporalTx, br services.FullBl if err != nil { return nil, err } - return CreateStateReaderFromBlockNumber(ctx, tx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ctx, br)), blockNumber, latest, txnIndex, stateCache, chainName) + return CreateStateReaderFromBlockNumber(ctx, tx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ctx, br)), blockNumber, latest, txnIndex, stateCache, chainName) } func CreateStateReaderFromBlockNumber(ctx context.Context, tx kv.TemporalTx, txNumsReader rawdbv3.TxNumsReader, blockNumber uint64, latest bool, txnIndex int, stateCache kvcache.Cache, chainName string) (state.StateReader, error) { @@ -200,7 +200,7 @@ func NewLatestDomainStateReader(sd *state2.SharedDomains) state.StateReader { } func NewLatestDomainStateWriter(domains *state2.SharedDomains, blockReader services.FullBlockReader, blockNum uint64) state.StateWriter { - minTxNum, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(context.Background(), blockReader)).Min(domains.Tx(), blockNum) + minTxNum, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(context.Background(), blockReader)).Min(domains.Tx(), blockNum) if err != nil { panic(err) } @@ -214,7 +214,7 @@ func NewLatestStateReader(tx kv.Tx) state.StateReader { } func NewLatestStateWriter(txc wrap.TxContainer, blockReader services.FullBlockReader, blockNum uint64) state.StateWriter { domains := txc.Doms - minTxNum, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(context.Background(), blockReader)).Min(domains.Tx(), blockNum) + minTxNum, err := rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(context.Background(), blockReader)).Min(domains.Tx(), blockNum) if err != nil { panic(err) } diff --git a/turbo/snapshotsync/freezeblocks/block_cache_test.go b/turbo/snapshotsync/freezeblocks/block_cache_test.go new file mode 100644 index 00000000000..292b392c964 --- /dev/null +++ b/turbo/snapshotsync/freezeblocks/block_cache_test.go @@ -0,0 +1,47 @@ +package freezeblocks + +import ( + "testing" + + "github.com/erigontech/erigon/turbo/snapshotsync" + "github.com/stretchr/testify/require" +) + +func TestCache2(t *testing.T) { + ran := snapshotsync.NewRange(10000, 10500) + // assume block i has maxTxNum = 200*(2+i-ran.from) (inclusive) + cacheMiss := 0 + b2tx := func(blk uint64) (uint64, error) { + cacheMiss++ + // assume expensive op + return 200 * (2 + blk - ran.From()), nil + } + + b2txNoErr := func(blk uint64) uint64 { + blk, _ = b2tx(blk) + return blk + } + + test := func(queryTxNum, expectedBlk, cacheMissFirstTime, cacheMissSecondTime uint64) { + cache := NewBlockTxNumLookupCache(100) + //q := cache.NewQuery(ran) + cacheMiss = 0 + answer, err := cache.Find(ran, queryTxNum, b2tx) + require.NoError(t, err) + require.Equal(t, cacheMissFirstTime, uint64(cacheMiss)) + require.Equal(t, expectedBlk, answer) + + cacheMiss = 0 + answer, err = cache.Find(ran, queryTxNum, b2tx) + require.NoError(t, err) + require.Equal(t, expectedBlk, answer) + require.Equal(t, uint64(cacheMiss), cacheMissSecondTime) + } + + test(b2txNoErr(10015)-100, uint64(10015), 10, 7) + test(400, uint64(10000), 3, 0) // queries 10000, which is cached + test(b2txNoErr(10499), uint64(10499), 9, 6) + test(b2txNoErr(10499)-200, uint64(10498), 10, 7) + test(b2txNoErr(10250), uint64(10250), 9, 6) + test(b2txNoErr(10488), uint64(10488), 10, 7) +} diff --git a/turbo/snapshotsync/freezeblocks/block_reader.go b/turbo/snapshotsync/freezeblocks/block_reader.go index 1db68750953..aa106858da1 100644 --- a/turbo/snapshotsync/freezeblocks/block_reader.go +++ b/turbo/snapshotsync/freezeblocks/block_reader.go @@ -1049,6 +1049,10 @@ func (r *BlockReader) bodyForStorageFromSnapshot(blockHeight uint64, sn *snapsho } }() // avoid crash because Erigon's core does many things + return BodyForStorageFromSnapshot(blockHeight, sn, buf) +} + +func BodyForStorageFromSnapshot(blockHeight uint64, sn *snapshotsync.VisibleSegment, buf []byte) (*types.BodyForStorage, []byte, error) { index := sn.Src().Index() if index == nil { @@ -1628,24 +1632,106 @@ func (r *BlockReader) Integrity(ctx context.Context) error { return nil } -func ReadTxNumFuncFromBlockReader(ctx context.Context, r services.FullBlockReader) rawdbv3.ReadTxNumFunc { - return func(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint64, ok bool, err error) { - maxTxNum, ok, err = rawdbv3.DefaultReadTxNumFunc(tx, c, blockNum) +func TxBlockIndexFromBlockReader(ctx context.Context, r services.FullBlockReader) rawdbv3.TxBlockIndex { + return &txBlockIndexWithBlockReader{ + r: r, + ctx: ctx, + cache: NewBlockTxNumLookupCache(20), + } +} + +type txBlockIndexWithBlockReader struct { + r services.FullBlockReader + ctx context.Context + cache *BlockTxNumLookupCache +} + +func (t *txBlockIndexWithBlockReader) MaxTxNum(tx kv.Tx, c kv.Cursor, blockNum uint64) (maxTxNum uint64, ok bool, err error) { + maxTxNum, ok, err = rawdbv3.DefaultTxBlockIndexInstance.MaxTxNum(tx, c, blockNum) + if err != nil { + return + } + r := t.r + if ok || r == nil { + return + } + b, err := r.CanonicalBodyForStorage(t.ctx, tx, blockNum) + if err != nil { + return 0, false, err + } + if b == nil { + return 0, false, nil + } + ret := b.BaseTxnID.U64() + uint64(b.TxCount) - 1 + return ret, true, nil +} + +func (t *txBlockIndexWithBlockReader) BlockNumber(tx kv.Tx, txNum uint64) (blockNum uint64, ok bool, err error) { + var buf []byte + var b *types.BodyForStorage + view := t.r.Snapshots().(*RoSnapshots).View() + defer view.Close() + + getMaxTxNum := func(seg *snapshotsync.VisibleSegment) GetMaxTxNum { + return func(i uint64) (uint64, error) { + b, buf, err = BodyForStorageFromSnapshot(i, seg, buf) + if err != nil { + return 0, err + } + if b == nil { + return 0, fmt.Errorf("BlockReader.BlockNumber: empty block: %d in file bodies.seg(%d-%d)", i, seg.From(), seg.To()) + } + return b.BaseTxnID.U64() + uint64(b.TxCount) - 1, nil + } + } + + // find the file + bodies := view.Bodies() + cache := t.cache + var maxTxNum uint64 + + blockIndex := sort.Search(len(bodies), func(i int) bool { if err != nil { - return + return true } - if ok || r == nil { - return + seg := bodies[i] + ran := seg.Range + maxTxNum, err = cache.GetLastMaxTxNum(ran, getMaxTxNum(seg)) + if err != nil { + return true } - b, err := r.CanonicalBodyForStorage(ctx, tx, blockNum) + return maxTxNum >= txNum + }) + + if err != nil { + return 0, false, err + } + + if blockIndex == len(bodies) { + // not in snapshots + blockNum, ok, err = rawdbv3.DefaultTxBlockIndexInstance.BlockNumber(tx, txNum) if err != nil { - return 0, false, err + return } - if b == nil { - return 0, false, nil + r := t.r + if ok || r == nil { + return } - ret := b.BaseTxnID.U64() + uint64(b.TxCount) - 1 - return ret, true, nil + + return 0, false, nil + } + + seg := bodies[blockIndex] + ran := seg.Range + + blkNum, err := cache.Find(ran, txNum, getMaxTxNum(seg)) + if err != nil { + return 0, false, err + } + + if blkNum >= ran.To() { + return 0, false, fmt.Errorf("BlockReader.BlockNumber: not found block: %d in file (%d-%d), searching for %d", blkNum, ran.From(), ran.To(), txNum) } + return blkNum, true, nil } diff --git a/turbo/snapshotsync/freezeblocks/block_snapshots.go b/turbo/snapshotsync/freezeblocks/block_snapshots.go index aad87b2829c..8cb0164b9cc 100644 --- a/turbo/snapshotsync/freezeblocks/block_snapshots.go +++ b/turbo/snapshotsync/freezeblocks/block_snapshots.go @@ -1083,6 +1083,11 @@ func RemoveIncompatibleIndices(dirs datadir.Dirs) error { } else { log.Info("Removing incompatible index", "file", fName) } + if err = os.Remove(fPath + ".torrent"); err != nil { + log.Warn("Removing incompatible index", "file", fName, "err", err) + } else { + log.Info("Removing incompatible index", "file", fName) + } continue } return fmt.Errorf("%w, %s", err, fPath) diff --git a/turbo/snapshotsync/freezeblocks/block_txnum_cache.go b/turbo/snapshotsync/freezeblocks/block_txnum_cache.go new file mode 100644 index 00000000000..8e0ae57bb01 --- /dev/null +++ b/turbo/snapshotsync/freezeblocks/block_txnum_cache.go @@ -0,0 +1,155 @@ +package freezeblocks + +import ( + "fmt" + "math" + "sort" + "sync" + "sync/atomic" + + "github.com/erigontech/erigon/turbo/snapshotsync" +) + +const E2StepSize = 1_000 + +type GetMaxTxNum func(blockNum uint64) (uint64, error) + +// BlockTxNumLookupCache implements a partial lookup table +// to find answer "what's the block number for this txNum" +type BlockTxNumLookupCache struct { + mu sync.RWMutex + cache map[snapshotsync.Range][]atomic.Uint64 + cadence float64 +} + +func NewBlockTxNumLookupCache(cadence int) *BlockTxNumLookupCache { + if E2StepSize%cadence != 0 { + panic(fmt.Sprintf("E2StepSize (%d) must be divisible by cadence (%d)", E2StepSize, cadence)) + } + + return &BlockTxNumLookupCache{ + cache: make(map[snapshotsync.Range][]atomic.Uint64), + cadence: float64(cadence), + } +} + +// ensureLookup creates the lookup table if it doesn't exist +func (c *BlockTxNumLookupCache) ensureLookup(r snapshotsync.Range) []atomic.Uint64 { + // lookup[i] stores maxTxNum for blockNumber = from + i * cadence for i in [0, size-2] + // lookup[size-1] stores maxTxNum for blockNumber = to - 1 + + // Try read lock first for common case + c.mu.RLock() + if lookup, ok := c.cache[r]; ok { + c.mu.RUnlock() + return lookup + } + c.mu.RUnlock() + + // Need to create - acquire write lock + c.mu.Lock() + defer c.mu.Unlock() + + // Double-check after acquiring write lock + if lookup, ok := c.cache[r]; ok { + return lookup + } + + rangeLen := float64(r.To() - r.From()) + size := int(rangeLen/c.cadence) + 1 + + lookup := make([]atomic.Uint64, size) + for i := range lookup { + lookup[i].Store(math.MaxUint64) + } + + c.cache[r] = lookup + return lookup +} + +func (c *BlockTxNumLookupCache) Find(r snapshotsync.Range, queryTxNum uint64, getter GetMaxTxNum) (blockNum uint64, err error) { + // assumption...txNum is in the given range + + // i) first find lookup block index at which queryTxNum might reside. This will be a range of blocks. This step uses lookup cache + // ii) then in that narrow block range, search for block containing queryTxNum. This cannot employ cache. + lookup := c.ensureLookup(r) + var txNum uint64 + lookupIndex := sort.Search(len(lookup), func(i int) bool { + if err != nil { + return true + } + txNum = lookup[i].Load() + if txNum == math.MaxUint64 { + // not found + blkNum := c.index2BlkNum(r.From(), len(lookup), i) + txNum, err = getter(blkNum) + if err != nil { + return true + } + lookup[i].Store(txNum) + } + + return txNum >= queryTxNum + + }) + + if err != nil { + return 0, err + } + + if lookupIndex == 0 { + return r.From(), nil + } + + from, to := c.index2BlkNum(r.From(), len(lookup), lookupIndex-1), c.index2BlkNum(r.From(), len(lookup), lookupIndex) + rangeFrom := from + + for from < to { + h := (from + to) >> 1 + + if h == rangeFrom { + txNum = lookup[lookupIndex-1].Load() + } else { + txNum, err = getter(h) + if err != nil { + return 0, err + } + } + + if txNum >= queryTxNum { + to = h + } else { + from = h + 1 + } + + } + + return from, nil +} + +func (c *BlockTxNumLookupCache) GetLastMaxTxNum(r snapshotsync.Range, getter GetMaxTxNum) (maxTxNum uint64, err error) { + lookup := c.ensureLookup(r) + lastIdx := len(lookup) - 1 + maxTxNum = lookup[lastIdx].Load() + if maxTxNum != math.MaxUint64 { + return maxTxNum, nil + } + + maxTxNum, err = getter(r.To() - 1) + if err != nil { + return 0, err + } + lookup[lastIdx].Store(maxTxNum) + + return maxTxNum, nil +} + +func (c *BlockTxNumLookupCache) index2BlkNum(from uint64, lookupSize, idxlookupIdx int) uint64 { + if idxlookupIdx >= lookupSize { + panic(fmt.Sprintf("index2BlkNum: idxlookupIdx (%d) >= lookupSize (%d)", idxlookupIdx, lookupSize)) + } + if idxlookupIdx == lookupSize-1 { + return from + uint64(idxlookupIdx)*uint64(c.cadence) - 1 + } + return from + uint64(idxlookupIdx)*uint64(c.cadence) +} diff --git a/turbo/snapshotsync/freezeblocks/bor_snapshots.go b/turbo/snapshotsync/freezeblocks/bor_snapshots.go index 79d9e8861ff..91afb18f49f 100644 --- a/turbo/snapshotsync/freezeblocks/bor_snapshots.go +++ b/turbo/snapshotsync/freezeblocks/bor_snapshots.go @@ -205,8 +205,10 @@ func removeBorOverlaps(dir string, active []snaptype.FileInfo, _max uint64) { for _, f := range toDel { _ = os.Remove(f) + _ = os.Remove(f + ".torrent") ext := filepath.Ext(f) withoutExt := f[:len(f)-len(ext)] _ = os.Remove(withoutExt + ".idx") + _ = os.Remove(withoutExt + ".idx.torrent") } } diff --git a/turbo/snapshotsync/merger.go b/turbo/snapshotsync/merger.go index 93209ef583f..11fd85b9622 100644 --- a/turbo/snapshotsync/merger.go +++ b/turbo/snapshotsync/merger.go @@ -52,7 +52,7 @@ func (m *Merger) FindMergeRanges(currentRanges []Range, maxBlockNum uint64) (toM } aggFrom := r.To() - span toMerge = append(toMerge, NewRange(aggFrom, r.To())) - for currentRanges[i].From() > aggFrom { + for i >= 0 && currentRanges[i].From() > aggFrom { i-- } break @@ -99,9 +99,11 @@ func (m *Merger) mergeSubSegment(ctx context.Context, v *View, sn snaptype.FileI ext := filepath.Ext(f) withoutExt := f[:len(f)-len(ext)] _ = os.Remove(withoutExt + ".idx") + _ = os.Remove(withoutExt + ".idx.torrent") isTxnType := strings.HasSuffix(withoutExt, coresnaptype.Transactions.Name()) if isTxnType { _ = os.Remove(withoutExt + "-to-block.idx") + _ = os.Remove(withoutExt + "-to-block.idx.torrent") } } }() @@ -203,7 +205,7 @@ func (m *Merger) Merge(ctx context.Context, snapshots *RoSnapshots, snapTypes [] } func (m *Merger) integrateMergedDirtyFiles(snapshots *RoSnapshots, in, out map[snaptype.Enum][]*DirtySegment) { - defer snapshots.recalcVisibleFiles() + defer snapshots.recalcVisibleFiles(snapshots.alignMin) snapshots.dirtyLock.Lock() defer snapshots.dirtyLock.Unlock() diff --git a/turbo/snapshotsync/snapshots.go b/turbo/snapshotsync/snapshots.go index ea68f769bdc..8db90bffb8c 100644 --- a/turbo/snapshotsync/snapshots.go +++ b/turbo/snapshotsync/snapshots.go @@ -496,7 +496,7 @@ func (s *RoTx) Close() { type BlockSnapshots interface { LogStat(label string) OpenFolder() error - OpenSegments(types []snaptype.Type, allowGaps bool) error + OpenSegments(types []snaptype.Type, allowGaps, allignMin bool) error SegmentsMax() uint64 SegmentsMin() uint64 Delete(fileName string) error @@ -568,7 +568,7 @@ func newRoSnapshots(cfg ethconfig.BlocksFreezing, snapDir string, types []snapty } s.segmentsMin.Store(segmentsMin) - s.recalcVisibleFiles() + s.recalcVisibleFiles(s.alignMin) if cfg.NoDownloader { s.DownloadComplete() @@ -811,7 +811,7 @@ func RecalcVisibleSegments(dirtySegments *btree.BTreeG[*DirtySegment]) []*Visibl return newVisibleSegments } -func (s *RoSnapshots) recalcVisibleFiles() { +func (s *RoSnapshots) recalcVisibleFiles(alignMin bool) { defer func() { s.idxMax.Store(s.idxAvailability()) }() @@ -832,12 +832,12 @@ func (s *RoSnapshots) recalcVisibleFiles() { if len(newVisibleSegments) > 0 { to = newVisibleSegments[len(newVisibleSegments)-1].to - 1 } - if s.alignMin { + if alignMin { maxVisibleBlocks = append(maxVisibleBlocks, to) } } - if s.alignMin { + if alignMin { // all types must have same hight minMaxVisibleBlock := slices.Min(maxVisibleBlocks) for _, t := range s.enums { @@ -974,7 +974,7 @@ func (s *RoSnapshots) OpenFiles() (list []string) { // OpenList stops on optimistic=false, continue opening files on optimistic=true func (s *RoSnapshots) OpenList(fileNames []string, optimistic bool) error { - defer s.recalcVisibleFiles() + defer s.recalcVisibleFiles(s.alignMin) s.dirtyLock.Lock() defer s.dirtyLock.Unlock() @@ -997,7 +997,7 @@ func (s *RoSnapshots) InitSegments(fileNames []string) error { return err } - s.recalcVisibleFiles() + s.recalcVisibleFiles(s.alignMin) wasReady := s.segmentsReady.Swap(true) if !wasReady { if s.downloadReady.Load() { @@ -1167,7 +1167,7 @@ func (s *RoSnapshots) OpenFolder() error { return err } - s.recalcVisibleFiles() + s.recalcVisibleFiles(s.alignMin) wasReady := s.segmentsReady.Swap(true) if !wasReady { if s.downloadReady.Load() { @@ -1177,8 +1177,8 @@ func (s *RoSnapshots) OpenFolder() error { return nil } -func (s *RoSnapshots) OpenSegments(types []snaptype.Type, allowGaps bool) error { - defer s.recalcVisibleFiles() +func (s *RoSnapshots) OpenSegments(types []snaptype.Type, allowGaps, alignMin bool) error { + defer s.recalcVisibleFiles(alignMin) s.dirtyLock.Lock() defer s.dirtyLock.Unlock() @@ -1204,7 +1204,7 @@ func (s *RoSnapshots) Close() { if s == nil { return } - defer s.recalcVisibleFiles() + defer s.recalcVisibleFiles(s.alignMin) s.dirtyLock.Lock() defer s.dirtyLock.Unlock() @@ -1359,7 +1359,7 @@ func (s *RoSnapshots) Delete(fileName string) error { v := s.View() defer v.Close() - defer s.recalcVisibleFiles() + defer s.recalcVisibleFiles(s.alignMin) if err := s.delete(fileName); err != nil { return fmt.Errorf("can't delete file: %w", err) } @@ -1670,9 +1670,11 @@ func removeOldFiles(toDel []string, snapDir string) { ext := filepath.Ext(f) withoutExt := f[:len(f)-len(ext)] _ = os.Remove(withoutExt + ".idx") + _ = os.Remove(withoutExt + ".idx.torrent") isTxnType := strings.HasSuffix(withoutExt, coresnaptype.Transactions.Name()) if isTxnType { _ = os.Remove(withoutExt + "-to-block.idx") + _ = os.Remove(withoutExt + "-to-block.idx.torrent") } } tmpFiles, err := snaptype.TmpFiles(snapDir) diff --git a/turbo/snapshotsync/snapshots_test.go b/turbo/snapshotsync/snapshots_test.go index 5e777c1433b..dc46f99d7c3 100644 --- a/turbo/snapshotsync/snapshots_test.go +++ b/turbo/snapshotsync/snapshots_test.go @@ -227,7 +227,7 @@ func TestMergeSnapshots(t *testing.T) { { merger := NewMerger(dir, 1, log.LvlInfo, nil, params.MainnetChainConfig, logger) merger.DisableFsync() - s.OpenSegments(coresnaptype.BlockSnapshotTypes, false) + s.OpenSegments(coresnaptype.BlockSnapshotTypes, false, true) Ranges := merger.FindMergeRanges(s.Ranges(), s.SegmentsMax()) require.Equal(3, len(Ranges)) err := merger.Merge(context.Background(), s, coresnaptype.BlockSnapshotTypes, Ranges, s.Dir(), false, nil, nil) @@ -376,7 +376,7 @@ func TestRemoveOverlaps(t *testing.T) { //corner case: small header.seg was removed, but header.idx left as garbage. such garbage must be cleaned. os.Remove(filepath.Join(s.Dir(), list[15].Name())) - require.NoError(s.OpenSegments(coresnaptype.BlockSnapshotTypes, false)) + require.NoError(s.OpenSegments(coresnaptype.BlockSnapshotTypes, false, true)) require.NoError(s.RemoveOverlaps()) list, err = snaptype.Segments(s.Dir()) @@ -420,7 +420,7 @@ func TestRemoveOverlaps_CrossingTypeString(t *testing.T) { require.NoError(err) require.Equal(4, len(list)) - require.NoError(s.OpenSegments(coresnaptype.BlockSnapshotTypes, false)) + require.NoError(s.OpenSegments(coresnaptype.BlockSnapshotTypes, false, true)) require.NoError(s.RemoveOverlaps()) list, err = snaptype.Segments(s.Dir()) @@ -488,7 +488,7 @@ func TestOpenAllSnapshot(t *testing.T) { err = s.OpenFolder() require.NoError(err) require.NotNil(s.visible[coresnaptype.Enums.Headers]) - s.OpenSegments(coresnaptype.BlockSnapshotTypes, false) + s.OpenSegments(coresnaptype.BlockSnapshotTypes, false, true) // require.Equal(1, len(getSegs(coresnaptype.Enums.Headers])) s.Close() diff --git a/turbo/snapshotsync/snapshotsync.go b/turbo/snapshotsync/snapshotsync.go index f3d95531286..360bcedb9f4 100644 --- a/turbo/snapshotsync/snapshotsync.go +++ b/turbo/snapshotsync/snapshotsync.go @@ -227,7 +227,7 @@ type blockReader interface { } // getMinimumBlocksToDownload - get the minimum number of blocks to download -func getMinimumBlocksToDownload(tx kv.Tx, blockReader blockReader, minStep uint64, blockPruneTo, historyPruneTo uint64) (uint64, uint64, error) { +func getMinimumBlocksToDownload(tx kv.Tx, blockReader blockReader, minStep uint64, historyPruneTo uint64) (uint64, uint64, error) { frozenBlocks := blockReader.Snapshots().SegmentsMax() minToDownload := uint64(math.MaxUint64) minStepToDownload := uint64(math.MaxUint32) @@ -327,7 +327,7 @@ func WaitForDownloader(ctx context.Context, logPrefix string, dirs datadir.Dirs, if err != nil { return err } - minBlockToDownload, minStepToDownload, err := getMinimumBlocksToDownload(tx, blockReader, minStep, blockPrune, historyPrune) + minBlockToDownload, minStepToDownload, err := getMinimumBlocksToDownload(tx, blockReader, minStep, historyPrune) if err != nil { return err } @@ -363,7 +363,7 @@ func WaitForDownloader(ctx context.Context, logPrefix string, dirs datadir.Dirs, if !syncCfg.KeepExecutionProofs && isStateHistory(p.Name) && strings.Contains(p.Name, kv.CommitmentDomain.String()) { continue } - if !syncCfg.PersistReceiptsCacheV2 && isStateHistory(p.Name) && strings.Contains(p.Name, kv.RCacheDomain.String()) { + if !syncCfg.PersistReceiptsCacheV2 && isStateSnapshot(p.Name) && strings.Contains(p.Name, kv.RCacheDomain.String()) { continue } diff --git a/turbo/stages/mock/mock_sentry.go b/turbo/stages/mock/mock_sentry.go index 2ddceb8d0f3..c52aae14652 100644 --- a/turbo/stages/mock/mock_sentry.go +++ b/turbo/stages/mock/mock_sentry.go @@ -29,7 +29,6 @@ import ( "time" "github.com/c2h5oh/datasize" - lru "github.com/hashicorp/golang-lru/arc/v2" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "golang.org/x/sync/errgroup" @@ -133,6 +132,7 @@ type MockSentry struct { HistoryV3 bool agg *libstate.Aggregator + cfg ethconfig.Config BlockSnapshots *freezeblocks.RoSnapshots BlockReader services.FullBlockReader ReceiptsReader *receipts.Generator @@ -284,6 +284,7 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK cfg.PersistReceiptsCacheV2 = true cfg.ChaosMonkey = false cfg.Snapshot.ChainName = gspec.Config.ChainName + cfg.Genesis = gspec logger := log.Root() logger.SetHandler(log.LvlFilterHandler(log.LvlError, log.StderrHandler)) @@ -316,6 +317,7 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK BlockReader: br, ReceiptsReader: receipts.NewGenerator(br, engine), HistoryV3: true, + cfg: cfg, } if tb != nil { @@ -433,12 +435,8 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK mock.sentriesClient.IsMock = true var ( - snapDb kv.RwDB snapDownloader = proto_downloader.NewMockDownloaderClient(ctrl) - - recents *lru.ARCCache[libcommon.Hash, *bor.Snapshot] - signatures *lru.ARCCache[libcommon.Hash, libcommon.Address] - blobStore services.BlobStorage + blobStore services.BlobStorage ) snapDownloader.EXPECT(). @@ -458,11 +456,6 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK Return(&proto_downloader.CompletedReply{Completed: true}, nil). AnyTimes() - if bor, ok := engine.(*bor.Bor); ok { - snapDb = bor.DB - recents = bor.Recents - signatures = bor.Signatures - } if parlia, ok := engine.(*parlia.Parlia); ok { blobStore = parlia.BlobStore mock.BlockReader.WithSidecars(blobStore) @@ -489,7 +482,6 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK cfg.Sync, stagedsync.MiningStages(mock.Ctx, stagedsync.StageMiningCreateBlockCfg(mock.DB, miner, *mock.ChainConfig, mock.Engine, nil, dirs.Tmp, mock.BlockReader), - stagedsync.StageBorHeimdallCfg(mock.DB, snapDb, miningStatePos, *mock.ChainConfig, nil, nil, nil, mock.BlockReader, nil, nil, recents, signatures, false, nil), stagedsync.StageExecuteBlocksCfg( mock.DB, prune, @@ -511,6 +503,7 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK stagedsync.StageSendersCfg(mock.DB, mock.ChainConfig, cfg.Sync, false, dirs.Tmp, prune, mock.BlockReader, mock.sentriesClient.Hd), stagedsync.StageMiningExecCfg(mock.DB, miner, nil, *mock.ChainConfig, mock.Engine, &vm.Config{}, dirs.Tmp, nil, 0, mock.TxPool, mock.BlockReader), stagedsync.StageMiningFinishCfg(mock.DB, *mock.ChainConfig, mock.Engine, miner, miningCancel, mock.BlockReader, latestBlockBuiltStore), + false, ), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder, logger, stages.ModeBlockProduction) // We start the mining step @@ -528,30 +521,34 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK mock.agg.SetProduceMod(mock.BlockReader.FreezingCfg().ProduceE3) mock.Sync = stagedsync.New( cfg.Sync, - stagedsync.DefaultStages(mock.Ctx, stagedsync.StageSnapshotsCfg(mock.DB, *mock.ChainConfig, cfg.Sync, dirs, blockRetire, snapDownloader, mock.BlockReader, mock.Notifications, mock.Engine, false, false, false, nil, prune), stagedsync.StageHeadersCfg(mock.DB, mock.sentriesClient.Hd, mock.sentriesClient.Bd, *mock.ChainConfig, cfg.Sync, sendHeaderRequest, propagateNewBlockHashes, penalize, cfg.BatchSize, false, mock.BlockReader, blockWriter, dirs.Tmp, mock.Notifications), stagedsync.StageBorHeimdallCfg(mock.DB, snapDb, stagedsync.MiningState{}, *mock.ChainConfig, nil, nil, nil, mock.BlockReader, nil, nil, recents, signatures, false, nil), stagedsync.StageBlockHashesCfg(mock.DB, mock.Dirs.Tmp, mock.ChainConfig, blockWriter), stagedsync.StageBodiesCfg(mock.DB, nil, mock.sentriesClient.Bd, sendBodyRequest, penalize, blockPropagator, cfg.Sync.BodyDownloadTimeoutSeconds, *mock.ChainConfig, mock.BlockReader, blockWriter), stagedsync.StageSendersCfg(mock.DB, mock.ChainConfig, cfg.Sync, false, dirs.Tmp, prune, mock.BlockReader, mock.sentriesClient.Hd), stagedsync.StageExecuteBlocksCfg( - mock.DB, - prune, - cfg.BatchSize, - mock.ChainConfig, - mock.Engine, - &vm.Config{}, - mock.Notifications, - cfg.StateStream, - /*stateStream=*/ false, - dirs, - mock.BlockReader, - mock.sentriesClient.Hd, - mock.gspec, - cfg.Sync, - nil, - false, - ), stagedsync.StageTxLookupCfg(mock.DB, prune, dirs.Tmp, mock.ChainConfig.Bor, mock.BlockReader), stagedsync.StageFinishCfg(mock.DB, dirs.Tmp, forkValidator), !withPosDownloader), + stagedsync.DefaultStages(mock.Ctx, + stagedsync.StageSnapshotsCfg(mock.DB, *mock.ChainConfig, cfg.Sync, dirs, blockRetire, snapDownloader, mock.BlockReader, mock.Notifications, mock.Engine, false, false, false, nil, prune), + stagedsync.StageHeadersCfg(mock.DB, mock.sentriesClient.Hd, mock.sentriesClient.Bd, *mock.ChainConfig, cfg.Sync, sendHeaderRequest, propagateNewBlockHashes, penalize, cfg.BatchSize, false, mock.BlockReader, blockWriter, dirs.Tmp, mock.Notifications), + stagedsync.StageBlockHashesCfg(mock.DB, mock.Dirs.Tmp, mock.ChainConfig, blockWriter), + stagedsync.StageBodiesCfg(mock.DB, nil, mock.sentriesClient.Bd, sendBodyRequest, penalize, blockPropagator, cfg.Sync.BodyDownloadTimeoutSeconds, *mock.ChainConfig, mock.BlockReader, blockWriter), + stagedsync.StageSendersCfg(mock.DB, mock.ChainConfig, cfg.Sync, false, dirs.Tmp, prune, mock.BlockReader, mock.sentriesClient.Hd), stagedsync.StageExecuteBlocksCfg( + mock.DB, + prune, + cfg.BatchSize, + mock.ChainConfig, + mock.Engine, + &vm.Config{}, + mock.Notifications, + cfg.StateStream, + /*stateStream=*/ false, + dirs, + mock.BlockReader, + mock.sentriesClient.Hd, + mock.gspec, + cfg.Sync, + nil, + false, + ), stagedsync.StageTxLookupCfg(mock.DB, prune, dirs.Tmp, mock.ChainConfig.Bor, mock.BlockReader), stagedsync.StageFinishCfg(mock.DB, dirs.Tmp, forkValidator), !withPosDownloader), stagedsync.DefaultUnwindOrder, stagedsync.DefaultPruneOrder, logger, stages.ModeApplyingBlocks, ) - cfg.Genesis = gspec pipelineStages := stages2.NewPipelineStages(mock.Ctx, db, nil, &cfg, p2p.Config{}, mock.sentriesClient, mock.Notifications, snapDownloader, mock.BlockReader, blockRetire, nil, forkValidator, mock.Engine, logger, checkStateRoot) mock.posStagedSync = stagedsync.New(cfg.Sync, pipelineStages, stagedsync.PipelineUnwindOrder, stagedsync.PipelinePruneOrder, logger, stages.ModeApplyingBlocks) @@ -564,7 +561,6 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK cfg.Sync, stagedsync.MiningStages(mock.Ctx, stagedsync.StageMiningCreateBlockCfg(mock.DB, miner, *mock.ChainConfig, mock.Engine, nil, dirs.Tmp, mock.BlockReader), - stagedsync.StageBorHeimdallCfg(mock.DB, snapDb, miner, *mock.ChainConfig, nil, nil, nil, mock.BlockReader, nil, nil, recents, signatures, false, nil), stagedsync.StageExecuteBlocksCfg( mock.DB, prune, @@ -586,14 +582,13 @@ func MockWithEverything(tb testing.TB, gspec *types.Genesis, key *ecdsa.PrivateK stagedsync.StageSendersCfg(mock.DB, mock.ChainConfig, cfg.Sync, false, dirs.Tmp, prune, mock.BlockReader, mock.sentriesClient.Hd), stagedsync.StageMiningExecCfg(mock.DB, miner, nil, *mock.ChainConfig, mock.Engine, &vm.Config{}, dirs.Tmp, nil, 0, mock.TxPool, mock.BlockReader), stagedsync.StageMiningFinishCfg(mock.DB, *mock.ChainConfig, mock.Engine, miner, miningCancel, mock.BlockReader, latestBlockBuiltStore), + false, ), stagedsync.MiningUnwindOrder, stagedsync.MiningPruneOrder, logger, stages.ModeBlockProduction, ) - cfg.Genesis = gspec - mock.StreamWg.Add(1) go mock.sentriesClient.RecvMessageLoop(mock.Ctx, mock.SentryClient, &mock.ReceiveWg) mock.StreamWg.Wait() @@ -708,6 +703,7 @@ func (ms *MockSentry) numberOfPoWBlocks(chain *core.ChainPack) int { return chain.NumberOfPoWBlocks() } +func (ms *MockSentry) Cfg() ethconfig.Config { return ms.cfg } func (ms *MockSentry) insertPoWBlocks(chain *core.ChainPack) error { n := ms.numberOfPoWBlocks(chain) if n == 0 { @@ -877,7 +873,7 @@ func (ms *MockSentry) HeaderDownload() *headerdownload.HeaderDownload { } func (ms *MockSentry) NewHistoryStateReader(blockNum uint64, tx kv.TemporalTx) state.StateReader { - r, err := rpchelper.CreateHistoryStateReader(tx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.ReadTxNumFuncFromBlockReader(ms.Ctx, ms.BlockReader)), + r, err := rpchelper.CreateHistoryStateReader(tx, rawdbv3.TxNums.WithCustomReadTxNumFunc(freezeblocks.TxBlockIndexFromBlockReader(ms.Ctx, ms.BlockReader)), blockNum, 0, ms.ChainConfig.ChainName) if err != nil { panic(err) diff --git a/turbo/stages/stageloop.go b/turbo/stages/stageloop.go index 3f0efcee0cc..2803dd72f71 100644 --- a/turbo/stages/stageloop.go +++ b/turbo/stages/stageloop.go @@ -321,29 +321,23 @@ func StageLoopIteration(ctx context.Context, db kv.RwDB, txc wrap.TxContainer, s return nil } -func stagesHeadersAndFinish(db kv.RoDB, tx kv.Tx) (head, bor, fin uint64, gasUsed uint64, err error) { +func stagesHeadersAndFinish(db kv.RoDB, tx kv.Tx) (head, polygonSync, fin uint64, gasUsed uint64, err error) { if tx != nil { if fin, err = stages.GetStageProgress(tx, stages.Finish); err != nil { - return head, bor, fin, gasUsed, err + return head, polygonSync, fin, gasUsed, err } if head, err = stages.GetStageProgress(tx, stages.Headers); err != nil { - return head, bor, fin, gasUsed, err + return head, polygonSync, fin, gasUsed, err } - if bor, err = stages.GetStageProgress(tx, stages.BorHeimdall); err != nil { - return head, bor, fin, gasUsed, err - } - var polygonSync uint64 if polygonSync, err = stages.GetStageProgress(tx, stages.PolygonSync); err != nil { - return head, bor, fin, gasUsed, err + return head, polygonSync, fin, gasUsed, err } - // bor heimdall and polygon sync are mutually exclusive, bor heimdall will be removed soon - bor = max(bor, polygonSync) h := rawdb.ReadHeaderByNumber(tx, head) if h != nil { gasUsed = h.GasUsed } - return head, bor, fin, gasUsed, nil + return head, polygonSync, fin, gasUsed, nil } if err := db.View(context.Background(), func(tx kv.Tx) error { if fin, err = stages.GetStageProgress(tx, stages.Finish); err != nil { @@ -352,15 +346,9 @@ func stagesHeadersAndFinish(db kv.RoDB, tx kv.Tx) (head, bor, fin uint64, gasUse if head, err = stages.GetStageProgress(tx, stages.Headers); err != nil { return err } - if bor, err = stages.GetStageProgress(tx, stages.BorHeimdall); err != nil { - return err - } - var polygonSync uint64 if polygonSync, err = stages.GetStageProgress(tx, stages.PolygonSync); err != nil { return err } - bor = max(bor, polygonSync) - h := rawdb.ReadHeaderByNumber(tx, head) if h != nil { gasUsed = h.GasUsed @@ -368,9 +356,9 @@ func stagesHeadersAndFinish(db kv.RoDB, tx kv.Tx) (head, bor, fin uint64, gasUse // bor heimdall and polygon sync are mutually exclusive, bor heimdall will be removed soon return nil }); err != nil { - return head, bor, fin, gasUsed, err + return head, polygonSync, fin, gasUsed, err } - return head, bor, fin, gasUsed, nil + return head, polygonSync, fin, gasUsed, nil } type Hook struct { @@ -696,7 +684,6 @@ func NewDefaultStages(ctx context.Context, return stagedsync.DefaultStages(ctx, stagedsync.StageSnapshotsCfg(db, *controlServer.ChainConfig, cfg.Sync, dirs, blockRetire, snapDownloader, blockReader, notifications, engine, cfg.InternalCL && cfg.CaplinConfig.ArchiveBlocks, cfg.CaplinConfig.ArchiveBlobs || cfg.DisableBlobPrune, cfg.CaplinConfig.ArchiveStates, silkworm, cfg.Prune), stagedsync.StageHeadersCfg(db, controlServer.Hd, controlServer.Bd, *controlServer.ChainConfig, cfg.Sync, controlServer.SendHeaderRequest, controlServer.PropagateNewBlockHashes, controlServer.Penalize, cfg.BatchSize, p2pCfg.NoDiscovery, blockReader, blockWriter, dirs.Tmp, notifications), - stagedsync.StageBorHeimdallCfg(db, snapDb, stagedsync.MiningState{}, *controlServer.ChainConfig, heimdallClient, heimdallStore, bridgeStore, blockReader, controlServer.Hd, controlServer.Penalize, recents, signatures, cfg.WithHeimdallWaypointRecording, nil), stagedsync.StageBlockHashesCfg(db, dirs.Tmp, controlServer.ChainConfig, blockWriter), stagedsync.StageBodiesCfg(db, blobStore, controlServer.Bd, controlServer.SendBodyRequest, controlServer.Penalize, controlServer.BroadcastNewBlock, cfg.Sync.BodyDownloadTimeoutSeconds, *controlServer.ChainConfig, blockReader, blockWriter), stagedsync.StageSendersCfg(db, controlServer.ChainConfig, cfg.Sync, false, dirs.Tmp, cfg.Prune, blockReader, controlServer.Hd), @@ -790,6 +777,7 @@ func NewPolygonSyncStages( statusDataProvider *sentry.StatusDataProvider, stopNode func() error, engineAPISwitcher sync.EngineAPISwitcher, + minedBlockReg sync.MinedBlockObserverRegistrar, ) []*stagedsync.Stage { return stagedsync.PolygonSyncStages( ctx, @@ -826,6 +814,7 @@ func NewPolygonSyncStages( nil, /* userUnwindTypeOverrides */ notifications, engineAPISwitcher, + minedBlockReg, ), stagedsync.StageSendersCfg(db, chainConfig, config.Sync, false, config.Dirs.Tmp, config.Prune, blockReader, nil), stagedsync.StageExecuteBlocksCfg(db, config.Prune, config.BatchSize, chainConfig, consensusEngine, &vm.Config{}, notifications, config.StateStream, false, config.Dirs, blockReader, nil, config.Genesis, config.Sync, SilkwormForExecutionStage(silkworm, config), config.PolygonExtraReceipt), diff --git a/txnprovider/txpool/assemble.go b/txnprovider/txpool/assemble.go index 5d2dcdbc825..e23b33e3a2d 100644 --- a/txnprovider/txpool/assemble.go +++ b/txnprovider/txpool/assemble.go @@ -58,8 +58,10 @@ func Assemble( shanghaiTime := chainConfig.ShanghaiTime var agraBlock *big.Int + var bhilaiBlock *big.Int if chainConfig.Bor != nil { agraBlock = chainConfig.Bor.GetAgraBlock() + bhilaiBlock = chainConfig.Bor.GetBhilaiBlock() } cancunTime := chainConfig.CancunTime pragueTime := chainConfig.PragueTime @@ -79,6 +81,7 @@ func Assemble( *chainID, shanghaiTime, agraBlock, + bhilaiBlock, cancunTime, pragueTime, chainConfig.BlobSchedule, diff --git a/txnprovider/txpool/pool.go b/txnprovider/txpool/pool.go index 0b7d64f8954..ae24d35f9ce 100644 --- a/txnprovider/txpool/pool.go +++ b/txnprovider/txpool/pool.go @@ -56,6 +56,7 @@ import ( "github.com/erigontech/erigon-lib/kv/order" "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon/core/types" + "github.com/erigontech/erigon/params" "github.com/erigontech/erigon/txnprovider" "github.com/erigontech/erigon/txnprovider/txpool/txpoolcfg" ) @@ -139,6 +140,8 @@ type TxPool struct { isPostShanghai atomic.Bool agraBlock *uint64 isPostAgra atomic.Bool + bhilaiBlock *uint64 + isPostBhilai atomic.Bool cancunTime *uint64 isPostCancun atomic.Bool pragueTime *uint64 @@ -150,7 +153,7 @@ type TxPool struct { newSlotsStreams *NewSlotsStreams builderNotifyNewTxns func() logger log.Logger - auths map[common.Address]*metaTxn // All accounts with a pooled authorization + auths map[AuthAndNonce]*metaTxn // All authority accounts with a pooled authorization blobHashToTxn map[common.Hash]struct { index int txnHash common.Hash @@ -171,6 +174,7 @@ func New( chainID uint256.Int, shanghaiTime *big.Int, agraBlock *big.Int, + bhilaiBlock *big.Int, cancunTime *big.Int, pragueTime *big.Int, blobSchedule *chain.BlobSchedule, @@ -231,11 +235,11 @@ func New( builderNotifyNewTxns: builderNotifyNewTxns, newSlotsStreams: newSlotsStreams, logger: logger, - auths: map[common.Address]*metaTxn{}, - blobHashToTxn: map[common.Hash]struct { + auths: make(map[AuthAndNonce]*metaTxn), + blobHashToTxn: make(map[common.Hash]struct { index int txnHash common.Hash - }{}, + }), } if shanghaiTime != nil { @@ -252,6 +256,13 @@ func New( agraBlockU64 := agraBlock.Uint64() res.agraBlock = &agraBlockU64 } + if bhilaiBlock != nil { + if !bhilaiBlock.IsUint64() { + return nil, errors.New("bhilaiBlock overflow") + } + bhilaiBlockU64 := bhilaiBlock.Uint64() + res.bhilaiBlock = &bhilaiBlockU64 + } if cancunTime != nil { if !cancunTime.IsUint64() { return nil, errors.New("cancunTime overflow") @@ -694,8 +705,8 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf, availa best := p.pending.best - isShanghai := p.isShanghai() || p.isAgra() - isPrague := p.isPrague() + isEIP3860 := p.isShanghai() || p.isAgra() + isEIP7623 := p.isPrague() || p.isBhilai() txns.Resize(uint(min(n, len(best.ms)))) var toRemove []*metaTxn @@ -748,9 +759,9 @@ func (p *TxPool) best(ctx context.Context, n int, txns *TxnsRlp, onTopOf, availa // make sure we have enough gas in the caller to add this transaction. // not an exact science using intrinsic gas but as close as we could hope for at // this stage - authorizationLen := uint64(len(mt.TxnSlot.Authorities)) - intrinsicGas, floorGas, _ := fixedgas.CalcIntrinsicGas(uint64(mt.TxnSlot.DataLen), uint64(mt.TxnSlot.DataNonZeroLen), authorizationLen, uint64(mt.TxnSlot.AccessListAddrCount), uint64(mt.TxnSlot.AccessListStorCount), mt.TxnSlot.Creation, true, true, isShanghai, isPrague) - if isPrague && floorGas > intrinsicGas { + authorizationLen := uint64(len(mt.TxnSlot.AuthAndNonces)) + intrinsicGas, floorGas, _ := fixedgas.CalcIntrinsicGas(uint64(mt.TxnSlot.DataLen), uint64(mt.TxnSlot.DataNonZeroLen), authorizationLen, uint64(mt.TxnSlot.AccessListAddrCount), uint64(mt.TxnSlot.AccessListStorCount), mt.TxnSlot.Creation, true, true, isEIP3860, isEIP7623) + if isEIP7623 && floorGas > intrinsicGas { intrinsicGas = floorGas } if intrinsicGas > availableGas { @@ -854,8 +865,9 @@ func toBlobs(_blobs [][]byte) []gokzg4844.BlobRef { } func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.CacheView) txpoolcfg.DiscardReason { - isShanghai := p.isShanghai() || p.isAgra() - if isShanghai && txn.Creation && txn.DataLen > fixedgas.MaxInitCodeSize { + isEIP3860 := p.isShanghai() || p.isAgra() + isPrague := p.isPrague() || p.isBhilai() + if isEIP3860 && txn.Creation && txn.DataLen > params.MaxInitCodeSize { return txpoolcfg.InitCodeTooLarge // EIP-3860 } if txn.Type == BlobTxnType { @@ -907,9 +919,9 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache } } - authorizationLen := len(txn.Authorities) + authorizationLen := len(txn.AuthAndNonces) if txn.Type == SetCodeTxnType { - if !p.isPrague() { + if !isPrague { return txpoolcfg.TypeNotActivated } if txn.Creation { @@ -928,15 +940,15 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache return txpoolcfg.UnderPriced } - gas, floorGas, overflow := fixedgas.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), uint64(authorizationLen), uint64(txn.AccessListAddrCount), uint64(txn.AccessListStorCount), txn.Creation, true, true, isShanghai, p.isPrague()) - if p.isPrague() && floorGas > gas { + gas, floorGas, overflow := fixedgas.CalcIntrinsicGas(uint64(txn.DataLen), uint64(txn.DataNonZeroLen), uint64(authorizationLen), uint64(txn.AccessListAddrCount), uint64(txn.AccessListStorCount), txn.Creation, true, true, isEIP3860, isPrague) + if isPrague && floorGas > gas { gas = floorGas } if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas idHash=%x gas=%d", txn.IDHash, gas)) } - if overflow != false { + if overflow { if txn.Traced { p.logger.Info(fmt.Sprintf("TX TRACING: validateTx intrinsic gas calculated failed due to overflow idHash=%x", txn.IDHash)) } @@ -970,6 +982,7 @@ func (p *TxPool) validateTx(txn *TxnSlot, isLocal bool, stateCache kvcache.Cache } return txpoolcfg.NonceTooLow } + // Transactor should have enough funds to cover the costs total := requiredBalance(txn) if senderBalance.Cmp(total) < 0 { @@ -1042,20 +1055,20 @@ func (p *TxPool) isShanghai() bool { return isTimeBasedForkActivated(&p.isPostShanghai, p.shanghaiTime) } -func (p *TxPool) isAgra() bool { +func (p *TxPool) isBlockNumBasedForkActivated(isPostFlag *atomic.Bool, forkBlockNum *uint64) bool { // once this flag has been set for the first time we no longer need to check the block - set := p.isPostAgra.Load() + set := isPostFlag.Load() if set { return true } - if p.agraBlock == nil { + if forkBlockNum == nil { return false } - agraBlock := *p.agraBlock + forkBlock := *forkBlockNum - // a zero here means Agra is always active - if agraBlock == 0 { - p.isPostAgra.Swap(true) + // a zero here means the fork is always active + if forkBlock == 0 { + isPostFlag.Swap(true) return true } @@ -1069,15 +1082,23 @@ func (p *TxPool) isAgra() bool { if headBlock == nil || err != nil { return false } - // A new block is built on top of the head block, so when the head is agraBlock-1, - // the new block should use the Agra rules. - activated := (*headBlock + 1) >= agraBlock + // A new block is built on top of the head block, so when the head is forkBlock-1, + // the new block should use the new fork rules. + activated := (*headBlock + 1) >= forkBlock if activated { - p.isPostAgra.Swap(true) + isPostFlag.Swap(true) } return activated } +func (p *TxPool) isAgra() bool { + return p.isBlockNumBasedForkActivated(&p.isPostAgra, p.agraBlock) +} + +func (p *TxPool) isBhilai() bool { + return p.isBlockNumBasedForkActivated(&p.isPostBhilai, p.bhilaiBlock) +} + func (p *TxPool) isCancun() bool { return isTimeBasedForkActivated(&p.isPostCancun, p.cancunTime) } @@ -1464,35 +1485,32 @@ func (p *TxPool) addLocked(mt *metaTxn, announcements *Announcements) txpoolcfg. return txpoolcfg.FeeTooLow } - // Do not allow transaction from if sender has authority - addr, ok := p.senders.getAddr(mt.TxnSlot.SenderID) + // Do not allow transaction from this same (sender + nonce) if sender has existing pooled authorization as authority + senderAddr, ok := p.senders.senderID2Addr[mt.TxnSlot.SenderID] if !ok { p.logger.Info("senderID not registered, discarding transaction for safety") return txpoolcfg.InvalidSender } - if _, ok := p.auths[addr]; ok { + if _, ok := p.auths[AuthAndNonce{senderAddr.String(), mt.TxnSlot.Nonce}]; ok { return txpoolcfg.ErrAuthorityReserved } // Check if we have txn with same authorization in the pool if mt.TxnSlot.Type == SetCodeTxnType { - foundDuplicate := false - for _, a := range mt.TxnSlot.Authorities { - p.logger.Debug("setCodeTxn ", "authority", a.String()) - if _, ok := p.auths[*a]; ok { - foundDuplicate = true - p.logger.Debug("setCodeTxn ", "DUPLICATE authority", a.String(), "txn", fmt.Sprintf("%x", mt.TxnSlot.IDHash)) - break + for _, a := range mt.TxnSlot.AuthAndNonces { + // Self authorization nonce should be senderNonce + 1 + if a.authority == senderAddr.String() && a.nonce != mt.TxnSlot.Nonce+1 { + p.logger.Debug("Self authorization nonce should be senderNonce + 1", "authority", a.authority, "txn", fmt.Sprintf("%x", mt.TxnSlot.IDHash)) + return txpoolcfg.NonceTooLow } - } - - if foundDuplicate { - return txpoolcfg.ErrAuthorityReserved - } else { - for _, a := range mt.TxnSlot.Authorities { - p.auths[*a] = mt + if _, ok := p.auths[AuthAndNonce{a.authority, a.nonce}]; ok { + p.logger.Debug("setCodeTxn ", "DUPLICATE authority", a.authority, "at nonce", a.nonce, "txn", fmt.Sprintf("%x", mt.TxnSlot.IDHash)) + return txpoolcfg.ErrAuthorityReserved } } + for _, a := range mt.TxnSlot.AuthAndNonces { + p.auths[AuthAndNonce{a.authority, a.nonce}] = mt + } } hashStr := string(mt.TxnSlot.IDHash[:]) @@ -1538,8 +1556,8 @@ func (p *TxPool) discardLocked(mt *metaTxn, reason txpoolcfg.DiscardReason) { p.totalBlobsInPool.Store(t - uint64(len(mt.TxnSlot.BlobHashes))) } if mt.TxnSlot.Type == SetCodeTxnType { - for _, a := range mt.TxnSlot.Authorities { - delete(p.auths, *a) + for _, a := range mt.TxnSlot.AuthAndNonces { + delete(p.auths, a) } } } diff --git a/txnprovider/txpool/pool_fuzz_test.go b/txnprovider/txpool/pool_fuzz_test.go index da86d7496aa..8db9ee38d42 100644 --- a/txnprovider/txpool/pool_fuzz_test.go +++ b/txnprovider/txpool/pool_fuzz_test.go @@ -324,7 +324,7 @@ func FuzzOnNewBlocks(f *testing.F) { cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) err = pool.start(ctx) @@ -550,7 +550,7 @@ func FuzzOnNewBlocks(f *testing.F) { check(p2pReceived, TxnSlots{}, "after_flush") checkNotify(p2pReceived, TxnSlots{}, "after_flush") - p2, err := New(ctx, ch, db, coreDB, txpoolcfg.DefaultConfig, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + p2, err := New(ctx, ch, db, coreDB, txpoolcfg.DefaultConfig, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) p2.senders = pool.senders // senders are not persisted diff --git a/txnprovider/txpool/pool_test.go b/txnprovider/txpool/pool_test.go index 7561cb68735..d1f91409eec 100644 --- a/txnprovider/txpool/pool_test.go +++ b/txnprovider/txpool/pool_test.go @@ -60,7 +60,7 @@ func TestNonceFromAddress(t *testing.T) { db := memdb.NewTestPoolDB(t) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) var stateVersionID uint64 = 0 @@ -178,6 +178,134 @@ func TestNonceFromAddress(t *testing.T) { } func TestMultipleAuthorizations(t *testing.T) { + addrA := common.HexToAddress("0xa") + addrB := common.HexToAddress("0xb") + cases := []struct { + title string + sender common.Address + senderNonce uint64 + authority *common.Address + authNonce uint64 + feecap uint64 + tipcap uint64 + expectedReason txpoolcfg.DiscardReason + replacedAuth *AuthAndNonce + }{ + { + title: "a setcode txn with sender=A and authority=B", + sender: addrA, + senderNonce: 0, + authority: &addrB, + authNonce: 0, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.Success, + }, + { + title: "A's own authorization with correct sender nonce, and same nonce for authorization", + sender: addrA, + senderNonce: 1, + authority: &addrA, + authNonce: 1, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.NonceTooLow, + }, + { + title: "own authorization with correct sender nonce, and one higher authority nonce", + sender: addrA, + senderNonce: 1, + authority: &addrA, + authNonce: 2, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.Success, + }, + { + title: "A sends senderNonce=(prev auth nonce) wtih existing own authorization of A ", + sender: addrA, + senderNonce: 2, + authority: nil, + authNonce: 0, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.ErrAuthorityReserved, + }, + + { + title: "B sends with nonce as B's auth nonce of existing authorization sent by A", + sender: addrB, + senderNonce: 0, + authority: nil, + authNonce: 0, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.ErrAuthorityReserved, + }, + { + title: "B sends non-setcode txn senderNonce=(auth nonce + 1) with existing own authorization sent by A", + sender: addrB, + senderNonce: 1, + authority: nil, + authNonce: 0, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.Success, + }, + { + title: "B's existing authorization in pool, B sends new setcode txn with higher nonce and auth nonce", + sender: addrB, + senderNonce: 2, + authority: &addrB, + authNonce: 3, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.Success, + }, + { + title: "replace setcode txn sent By B with B's authorization, with higher tipcap and A's authorization", + sender: addrB, + senderNonce: 2, + authority: &addrA, + authNonce: 3, + feecap: 200_000, + tipcap: 200_000, + expectedReason: txpoolcfg.Success, + replacedAuth: &AuthAndNonce{addrB.String(), 3}, + }, + { + title: "B sends to replace own setcode txn with non setcode txn, with higher tipcap", + sender: addrB, + senderNonce: 2, + authority: nil, + authNonce: 0, + feecap: 300_000, + tipcap: 300_000, + expectedReason: txpoolcfg.Success, + replacedAuth: &AuthAndNonce{addrA.String(), 3}, + }, + { + title: "B sends to replace non setcode txn, with setcode txn (A's auth) with higher tipcap", + sender: addrB, + senderNonce: 2, + authority: &addrA, + authNonce: 3, + feecap: 400_000, + tipcap: 400_000, + expectedReason: txpoolcfg.Success, + }, + { + title: "B sends another setcode txn with B's authorization after setcode txn for A's authorization", + sender: addrB, + senderNonce: 3, + authority: &addrB, + authNonce: 4, + feecap: 100_000, + tipcap: 100_000, + expectedReason: txpoolcfg.Success, + }, + } + ch := make(chan Announcements, 100) coreDB, _ := temporaltest.NewTestDB(t, datadir.New(t.TempDir())) db := memdb.NewTestPoolDB(t) @@ -186,207 +314,76 @@ func TestMultipleAuthorizations(t *testing.T) { cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0 /* shanghaiTime */, nil /* agraBlock */, common.Big0 /* cancunTime */, common.Big0 /* pragueTime */, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0 /* shanghaiTime */, nil /* agraBlock */, nil /* bhiliaBlock */, common.Big0 /* cancunTime */, common.Big0 /* pragueTime */, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(t, err) require.True(t, pool != nil) var stateVersionID uint64 = 0 - pendingBaseFee := uint64(200000) - // start blocks from 0, set empty hash - then kvcache will also work on this + pendingBaseFee := uint64(50_000) h1 := gointerfaces.ConvertHashToH256([32]byte{}) change := &remote.StateChangeBatch{ StateVersionId: stateVersionID, PendingBlockBaseFee: pendingBaseFee, - BlockGasLimit: 1000000, + BlockGasLimit: 36_000_000, ChangeBatch: []*remote.StateChange{ {BlockHeight: 0, BlockHash: h1}, }, } - chainID := uint64(7078815900) - privateKey, err := crypto.GenerateKey() - assert.NoError(t, err) - authAddress := crypto.PubkeyToAddress(privateKey.PublicKey) - - var addr1, addr2 [20]byte - addr2[0] = 1 acc := accounts3.Account{ Nonce: 0, - Balance: *uint256.NewInt(1 * common.Ether), + Balance: *uint256.NewInt(10 * common.Ether), CodeHash: common.Hash{}, Incarnation: 1, } v := accounts3.SerialiseV3(&acc) change.ChangeBatch[0].Changes = append(change.ChangeBatch[0].Changes, &remote.AccountChange{ Action: remote.Action_UPSERT, - Address: gointerfaces.ConvertAddressToH160(addr1), - Data: v, - }) - change.ChangeBatch[0].Changes = append(change.ChangeBatch[0].Changes, &remote.AccountChange{ - Action: remote.Action_UPSERT, - Address: gointerfaces.ConvertAddressToH160(addr2), + Address: gointerfaces.ConvertAddressToH160(addrA), Data: v, }) change.ChangeBatch[0].Changes = append(change.ChangeBatch[0].Changes, &remote.AccountChange{ Action: remote.Action_UPSERT, - Address: gointerfaces.ConvertAddressToH160(authAddress), + Address: gointerfaces.ConvertAddressToH160(addrB), Data: v, }) + tx, err := db.BeginRw(ctx) require.NoError(t, err) defer tx.Rollback() err = pool.OnNewBlock(ctx, change, TxnSlots{}, TxnSlots{}, TxnSlots{}) assert.NoError(t, err) - // Generate auth data for transactions - var b [33]byte - data := bytes.NewBuffer(b[:]) - data.Reset() - - authLen := rlp.U64Len(chainID) - authLen += 1 + length.Addr - authLen += rlp.U64Len(0) - assert.NoError(t, rlp.EncodeStructSizePrefix(authLen, data, b[:])) - assert.NoError(t, rlp.EncodeInt(chainID, data, b[:])) - assert.NoError(t, rlp.EncodeOptionalAddress(&authAddress, data, b[:])) - assert.NoError(t, rlp.EncodeInt(0, data, b[:])) - - hashData := []byte{params.SetCodeMagicPrefix} - hashData = append(hashData, data.Bytes()...) - hash := crypto.Keccak256Hash(hashData) - - sig, err := crypto.Sign(hash.Bytes(), privateKey) - assert.NoError(t, err) - - r := uint256.NewInt(0).SetBytes(sig[:32]) - s := uint256.NewInt(0).SetBytes(sig[32:64]) - yParity := sig[64] - - var auth Signature - auth.ChainID.Set(uint256.NewInt(chainID)) - auth.V.Set(uint256.NewInt(uint64(yParity))) - auth.R.Set(r) - auth.S.Set(s) - - logger := log.New() - - // a new txn with authority same as one in an existing authorization should not be accepted - { - var txnSlots TxnSlots - txnSlot1 := &TxnSlot{ - Tip: *uint256.NewInt(300000), - FeeCap: *uint256.NewInt(300000), - Gas: 100000, - Nonce: 0, - Authorities: []*common.Address{&authAddress}, - Type: SetCodeTxnType, - } - txnSlot1.IDHash[0] = 1 - txnSlots.Append(txnSlot1, addr1[:], true) - reasons, err := pool.AddLocalTxns(ctx, txnSlots) - require.NoError(t, err) - assert.Equal(t, reasons, []txpoolcfg.DiscardReason{txpoolcfg.Success}) - - txnSlots = TxnSlots{} - txnSlot2 := &TxnSlot{ - Tip: *uint256.NewInt(300000), - FeeCap: *uint256.NewInt(300000), - Gas: 100000, - Nonce: 0, - Authorities: []*common.Address{&authAddress}, - Type: SetCodeTxnType, - } - txnSlot2.IDHash[0] = 2 - txnSlots.Append(txnSlot2, addr2[:], true) - require.NoError(t, pool.senders.registerNewSenders(&txnSlots, logger)) - reasons, err = pool.AddLocalTxns(ctx, txnSlots) - require.NoError(t, err) - assert.Equal(t, reasons, []txpoolcfg.DiscardReason{txpoolcfg.ErrAuthorityReserved}) - - assert.Len(t, pool.auths, 1) // auth address should be in pool auth - _, ok := pool.auths[authAddress] - assert.True(t, ok) - - err = pool.OnNewBlock(ctx, change, TxnSlots{}, TxnSlots{}, TxnSlots{[]*TxnSlot{txnSlot1}, Addresses{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []bool{true}}) - assert.NoError(t, err) - - assert.Len(t, pool.auths, 0) // auth address should not be there after block has been mined - } - - // fee bump - { - var txnSlots TxnSlots - txnSlot1 := &TxnSlot{ - Tip: *uint256.NewInt(300000), - FeeCap: *uint256.NewInt(300000), - Gas: 100000, - Nonce: 1, - Authorities: []*common.Address{&authAddress}, - Type: SetCodeTxnType, - } - txnSlot1.IDHash[0] = 3 - txnSlots.Append(txnSlot1, addr1[:], true) - - assert.NoError(t, pool.senders.registerNewSenders(&txnSlots, logger)) - reasons, err := pool.AddLocalTxns(ctx, txnSlots) - assert.NoError(t, err) - assert.Equal(t, reasons, []txpoolcfg.DiscardReason{txpoolcfg.Success}) - - txnSlots = TxnSlots{} - txnSlot2 := &TxnSlot{ - Tip: *uint256.NewInt(900000), - FeeCap: *uint256.NewInt(900000), - Gas: 100000, - Nonce: 1, - Authorities: []*common.Address{&authAddress}, - Type: SetCodeTxnType, - } - txnSlot2.IDHash[0] = 4 - txnSlots.Append(txnSlot2, addr1[:], true) - - assert.NoError(t, pool.senders.registerNewSenders(&txnSlots, logger)) - reasons, err = pool.AddLocalTxns(ctx, txnSlots) - assert.NoError(t, err) - assert.Equal(t, reasons, []txpoolcfg.DiscardReason{txpoolcfg.Success}) - assert.Equal(t, pool.queued.Best().TxnSlot, txnSlot2) - - err = pool.OnNewBlock(ctx, change, TxnSlots{}, TxnSlots{}, TxnSlots{[]*TxnSlot{txnSlot1}, Addresses{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []bool{true}}) - assert.NoError(t, err) - } - - // do not allow transactions from a sender if there is a pending delegated txn its authority - { - var txnSlots TxnSlots - txnSlot1 := &TxnSlot{ - Tip: *uint256.NewInt(300000), - FeeCap: *uint256.NewInt(300000), - Gas: 100000, - Nonce: 1, - Authorities: []*common.Address{&authAddress}, - Type: SetCodeTxnType, - } - txnSlot1.IDHash[0] = 5 - txnSlots.Append(txnSlot1, addr1[:], true) - - assert.NoError(t, pool.senders.registerNewSenders(&txnSlots, logger)) - reasons, err := pool.AddLocalTxns(ctx, txnSlots) - assert.NoError(t, err) - assert.Equal(t, reasons, []txpoolcfg.DiscardReason{txpoolcfg.Success}) - - txnSlots = TxnSlots{} - txnSlot2 := &TxnSlot{ - Tip: *uint256.NewInt(300000), - FeeCap: *uint256.NewInt(300000), - Gas: 100000, - Nonce: 1, - Type: DynamicFeeTxnType, - } - txnSlot2.IDHash[0] = 6 - - txnSlots.Append(txnSlot2, authAddress.Bytes(), true) - reasons, err = pool.AddLocalTxns(ctx, txnSlots) - assert.NoError(t, err) - assert.Equal(t, reasons, []txpoolcfg.DiscardReason{txpoolcfg.ErrAuthorityReserved}) + idHash := 0 + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + t.Log("\n--- Testing " + c.title) + var txnSlots TxnSlots + txnSlot1 := &TxnSlot{ + Tip: *uint256.NewInt(c.tipcap), + FeeCap: *uint256.NewInt(c.feecap), + Gas: 100000, + Nonce: c.senderNonce, + } + if c.authority != nil { + txnSlot1.AuthAndNonces = []AuthAndNonce{{c.authority.String(), c.authNonce}} + txnSlot1.Type = SetCodeTxnType + } + txnSlot1.IDHash[0] = uint8(idHash) + idHash++ + txnSlots.Append(txnSlot1, c.sender[:], true) + reasons, err := pool.AddLocalTxns(ctx, txnSlots) + require.NoError(t, err) + assert.Equal(t, []txpoolcfg.DiscardReason{c.expectedReason}, reasons) + if c.authority != nil && c.expectedReason == txpoolcfg.Success { + _, ok := pool.auths[AuthAndNonce{c.authority.String(), c.authNonce}] + assert.True(t, ok) + } + if c.replacedAuth != nil { + _, ok := pool.auths[*c.replacedAuth] + assert.False(t, ok) + } + }) } } @@ -441,7 +438,7 @@ func TestReplaceWithHigherFee(t *testing.T) { t.Cleanup(cancel) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.NotEqual(nil, pool) var stateVersionID uint64 = 0 @@ -564,7 +561,7 @@ func TestReverseNonces(t *testing.T) { t.Cleanup(cancel) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) var stateVersionID uint64 = 0 @@ -694,7 +691,7 @@ func TestTxnPoke(t *testing.T) { t.Cleanup(cancel) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) var stateVersionID uint64 = 0 @@ -931,7 +928,7 @@ func TestShanghaiValidateTxn(t *testing.T) { asrt.NoError(err) defer sd.Close() cache := kvcache.NewDummy() - pool, err := New(ctx, ch, nil, coreDB, cfg, cache, *u256.N1, shanghaiTime, nil /* agraBlock */, nil /* cancunTime */, nil, nil, nil, nil, func() {}, nil, logger, WithFeeCalculator(nil)) + pool, err := New(ctx, ch, nil, coreDB, cfg, cache, *u256.N1, shanghaiTime, nil /* agraBlock */, nil /* cancunTime */, nil, nil, nil, nil, nil, func() {}, nil, logger, WithFeeCalculator(nil)) asrt.NoError(err) sndr := accounts3.Account{Nonce: 0, Balance: *uint256.NewInt(math.MaxUint64)} @@ -977,7 +974,7 @@ func TestTooHighGasLimitTxnValidation(t *testing.T) { t.Cleanup(cancel) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) var stateVersionID uint64 = 0 @@ -1042,7 +1039,7 @@ func TestSetCodeTxnValidationWithLargeAuthorizationValues(t *testing.T) { chainID := *maxUint256 cache := kvcache.NewDummy() logger := log.New() - pool, err := New(ctx, ch, nil, coreDB, cfg, cache, chainID, common.Big0 /* shanghaiTime */, nil, /* agraBlock */ + pool, err := New(ctx, ch, nil, coreDB, cfg, cache, chainID, common.Big0 /* shanghaiTime */, nil /* agraBlock */, nil, /* bhiliaBlock */ common.Big0 /* cancunTime */, common.Big0 /* pragueTime */, nil, nil, nil, func() {}, nil, logger, WithFeeCalculator(nil)) assert.NoError(t, err) pool.blockGasLimit.Store(30_000_000) @@ -1062,13 +1059,12 @@ func TestSetCodeTxnValidationWithLargeAuthorizationValues(t *testing.T) { assert.NoError(t, err) txn := &TxnSlot{ - FeeCap: *uint256.NewInt(21000), - Gas: 500000, - SenderID: 0, - Type: SetCodeTxnType, - Authorities: make([]*common.Address, 1), + FeeCap: *uint256.NewInt(21000), + Gas: 500000, + SenderID: 0, + Type: SetCodeTxnType, + AuthAndNonces: []AuthAndNonce{{nonce: 0, authority: common.Address{}.String()}}, } - txn.Authorities[0] = &common.Address{} txns := TxnSlots{ Txns: append([]*TxnSlot{}, txn), @@ -1093,7 +1089,7 @@ func TestBlobTxnReplacement(t *testing.T) { t.Cleanup(cancel) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0, nil, common.Big0, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0, nil, nil, common.Big0, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) @@ -1277,7 +1273,7 @@ func TestDropRemoteAtNoGossip(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - txnPool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, func() {}, nil, logger, WithFeeCalculator(nil)) + txnPool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, nil, func() {}, nil, logger, WithFeeCalculator(nil)) assert.NoError(err) require.True(txnPool != nil) @@ -1388,7 +1384,7 @@ func TestBlobSlots(t *testing.T) { cfg.TotalBlobPoolLimit = 20 sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0, nil, common.Big0, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0, nil, nil, common.Big0, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) var stateVersionID uint64 = 0 @@ -1471,7 +1467,7 @@ func TestGetBlobsV1(t *testing.T) { cfg.TotalBlobPoolLimit = 20 sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0, nil, common.Big0, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, common.Big0, nil, nil, common.Big0, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) pool.blockGasLimit.Store(30000000) @@ -1546,7 +1542,7 @@ func TestGasLimitChanged(t *testing.T) { db := memdb.NewTestPoolDB(t) cfg := txpoolcfg.DefaultConfig sendersCache := kvcache.New(kvcache.DefaultCoherentConfig) - pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) + pool, err := New(ctx, ch, db, coreDB, cfg, sendersCache, *u256.N1, nil, nil, nil, nil, nil, nil, nil, nil, func() {}, nil, log.New(), WithFeeCalculator(nil)) assert.NoError(err) require.True(pool != nil) var stateVersionID uint64 = 0 diff --git a/txnprovider/txpool/pool_txn_parser.go b/txnprovider/txpool/pool_txn_parser.go index 16d9260088a..ac884277fec 100644 --- a/txnprovider/txpool/pool_txn_parser.go +++ b/txnprovider/txpool/pool_txn_parser.go @@ -465,7 +465,7 @@ func (ctx *TxnParseContext) parseTransactionBody(payload []byte, pos, p0 int, sl p = dataPos + dataLen } if slot.Type == SetCodeTxnType { - slot.Authorities = make([]*common.Address, 0) + slot.AuthAndNonces = make([]AuthAndNonce, 0) dataPos, dataLen, err = rlp.ParseList(payload, p) if err != nil { return 0, fmt.Errorf("%w: authorizations len: %s", ErrParseTxn, err) //nolint @@ -509,7 +509,7 @@ func (ctx *TxnParseContext) parseTransactionBody(payload []byte, pos, p0 int, sl if err != nil { return 0, fmt.Errorf("%w: recover authorization signer: %s", ErrParseTxn, err) //nolint } - slot.Authorities = append(slot.Authorities, authority) + slot.AuthAndNonces = append(slot.AuthAndNonces, AuthAndNonce{authority.String(), auth.Nonce}) authPos += authLen if authPos != p2 { return 0, fmt.Errorf("%w: authorization: unexpected list items", ErrParseTxn) @@ -668,6 +668,11 @@ func (ctx *TxnParseContext) parseTransactionBody(payload []byte, pos, p0 int, sl return p, nil } +type AuthAndNonce struct { + authority string + nonce uint64 +} + // TxnSlot contains information extracted from an Ethereum transaction, which is enough to manage it inside the transaction. // Also, it contains some auxiliary information, like ephemeral fields, and indices within priority queues type TxnSlot struct { @@ -695,7 +700,7 @@ type TxnSlot struct { Commitments []gokzg4844.KZGCommitment Proofs []gokzg4844.KZGProof - Authorities []*common.Address // Indexed authorization signers for EIP-7702 txns (type-4) + AuthAndNonces []AuthAndNonce // Indexed authorization signers + nonces for EIP-7702 txns (type-4) } diff --git a/txnprovider/txpool/pool_txn_parser_test.go b/txnprovider/txpool/pool_txn_parser_test.go index 1a04472ac70..5166664092f 100644 --- a/txnprovider/txpool/pool_txn_parser_test.go +++ b/txnprovider/txpool/pool_txn_parser_test.go @@ -325,8 +325,8 @@ func TestSetCodeAuthSignatureRecover(t *testing.T) { setCodeTx := types.SetCodeTransaction{} rlpStream := rlp.NewStream(bytes.NewBuffer(txnRlpBytes[1:]), uint64(len(txnRlpBytes))) setCodeTx.DecodeRLP(rlpStream) - require.Len(t, txn.Authorities, 1) - require.Equal(t, expectedSigner, *txn.Authorities[0]) + require.Len(t, txn.AuthAndNonces, 1) + require.Equal(t, expectedSigner.String(), txn.AuthAndNonces[0].authority) } func TestSetCodeTxnParsing(t *testing.T) { @@ -344,7 +344,7 @@ func TestSetCodeTxnParsing(t *testing.T) { _, err = ctx.ParseTransaction(bodyRlx, 0, &txn, nil, hasEnvelope, false, nil) require.NoError(t, err) - assert.Equal(t, 2, len(txn.Authorities)) + assert.Len(t, txn.AuthAndNonces, 2) assert.Equal(t, SetCodeTxnType, txn.Type) // test empty authorizations @@ -360,7 +360,7 @@ func TestSetCodeTxnParsing(t *testing.T) { _, err = ctx.ParseTransaction(bodyRlx, 0, &tx2, nil, hasEnvelope, false, nil) require.NoError(t, err) - assert.Equal(t, 0, len(tx2.Authorities)) + assert.Empty(t, tx2.AuthAndNonces) assert.Equal(t, SetCodeTxnType, tx2.Type) // generated using this in core/types/encdec_test.go