From 6f831548e94950838d4f81b0acafd572d55fb9d1 Mon Sep 17 00:00:00 2001 From: Ostroukhov Nikita Date: Fri, 14 Feb 2025 15:22:11 +0000 Subject: [PATCH 01/42] Implemented wait if heimdall is not synced to the chain (#13807) --- cmd/devnet/services/polygon/heimdall.go | 4 ++ .../polygon/heimdallsim/heimdall_simulator.go | 4 ++ polygon/bor/bor_test.go | 4 ++ polygon/heimdall/client.go | 2 + polygon/heimdall/client_http.go | 22 ++++++++++ polygon/heimdall/client_mock.go | 39 ++++++++++++++++ polygon/heimdall/metrics.go | 1 + polygon/heimdall/service.go | 28 ++++++++++++ polygon/heimdall/service_test.go | 44 +++++++++++++++++++ polygon/heimdall/status.go | 30 +++++++++++++ polygon/sync/sync.go | 22 ++++++++++ 11 files changed, 200 insertions(+) create mode 100644 polygon/heimdall/status.go diff --git a/cmd/devnet/services/polygon/heimdall.go b/cmd/devnet/services/polygon/heimdall.go index 077db74c2c5..8eaebe924c8 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) FetchStatus(ctx context.Context) (*heimdall.Status, error) { + return nil, errors.New("TODO") +} + func (h *Heimdall) FetchCheckpoint(ctx context.Context, number int64) (*heimdall.Checkpoint, error) { return nil, errors.New("TODO") } diff --git a/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go b/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go index cd6a23a9866..4b30d3811ef 100644 --- a/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go +++ b/cmd/devnet/services/polygon/heimdallsim/heimdall_simulator.go @@ -228,6 +228,10 @@ func (h *HeimdallSimulator) FetchStateSyncEvent(ctx context.Context, id uint64) return nil, errors.New("method FetchStateSyncEvent not implemented") } +func (h *HeimdallSimulator) FetchStatus(ctx context.Context) (*heimdall.Status, error) { + return nil, errors.New("method FetchStatus not implemented") +} + func (h *HeimdallSimulator) FetchCheckpoint(ctx context.Context, number int64) (*heimdall.Checkpoint, error) { return nil, errors.New("method FetchCheckpoint not implemented") } diff --git a/polygon/bor/bor_test.go b/polygon/bor/bor_test.go index ac8221ac6fc..68787bf189d 100644 --- a/polygon/bor/bor_test.go +++ b/polygon/bor/bor_test.go @@ -79,6 +79,10 @@ func (h *test_heimdall) FetchStateSyncEvent(ctx context.Context, id uint64) (*he return nil, nil } +func (h *test_heimdall) FetchStatus(ctx context.Context) (*heimdall.Status, error) { + return nil, nil +} + func (h *test_heimdall) FetchSpan(ctx context.Context, spanID uint64) (*heimdall.Span, error) { if span, ok := h.spans[heimdall.SpanId(spanID)]; ok { diff --git a/polygon/heimdall/client.go b/polygon/heimdall/client.go index 1c68e412e7d..bc71f8b6b8c 100644 --- a/polygon/heimdall/client.go +++ b/polygon/heimdall/client.go @@ -30,6 +30,8 @@ type Client interface { FetchSpan(ctx context.Context, spanID uint64) (*Span, error) FetchSpans(ctx context.Context, page uint64, limit uint64) ([]*Span, error) + FetchStatus(ctx context.Context) (*Status, error) + FetchCheckpoint(ctx context.Context, number int64) (*Checkpoint, error) FetchCheckpointCount(ctx context.Context) (int64, error) FetchCheckpoints(ctx context.Context, page uint64, limit uint64) ([]*Checkpoint, error) diff --git a/polygon/heimdall/client_http.go b/polygon/heimdall/client_http.go index 852c83519f4..877a80a7187 100644 --- a/polygon/heimdall/client_http.go +++ b/polygon/heimdall/client_http.go @@ -125,6 +125,8 @@ const ( fetchStateSyncEventsPath = "clerk/event-record/list" fetchStateSyncEvent = "clerk/event-record/%s" + fetchStatus = "/status" + fetchCheckpoint = "/checkpoints/%s" fetchCheckpointCount = "/checkpoints/count" fetchCheckpointList = "/checkpoints/list" @@ -349,6 +351,22 @@ func (c *HttpClient) FetchMilestone(ctx context.Context, number int64) (*Milesto return &response.Result, nil } +func (c *HttpClient) FetchStatus(ctx context.Context) (*Status, error) { + url, err := statusURL(c.urlString) + if err != nil { + return nil, err + } + + ctx = withRequestType(ctx, statusRequest) + + response, err := FetchWithRetry[StatusResponse](ctx, c, url, c.logger) + if err != nil { + return nil, err + } + + return &response.Result, nil +} + // FetchCheckpointCount fetches the checkpoint count from heimdall func (c *HttpClient) FetchCheckpointCount(ctx context.Context) (int64, error) { url, err := checkpointCountURL(c.urlString) @@ -587,6 +605,10 @@ func checkpointCountURL(urlString string) (*url.URL, error) { return makeURL(urlString, fetchCheckpointCount, "") } +func statusURL(urlString string) (*url.URL, error) { + return makeURL(urlString, fetchStatus, "") +} + func checkpointListURL(urlString string, page uint64, limit uint64) (*url.URL, error) { return makeURL(urlString, fetchCheckpointList, fmt.Sprintf(fetchCheckpointListQueryFormat, page, limit)) } diff --git a/polygon/heimdall/client_mock.go b/polygon/heimdall/client_mock.go index 7710eff7509..a8f3baf9089 100644 --- a/polygon/heimdall/client_mock.go +++ b/polygon/heimdall/client_mock.go @@ -620,3 +620,42 @@ func (c *MockClientFetchStateSyncEventsCall) DoAndReturn(f func(context.Context, c.Call = c.Call.DoAndReturn(f) return c } + +// FetchStatus mocks base method. +func (m *MockClient) FetchStatus(ctx context.Context) (*Status, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchStatus", ctx) + ret0, _ := ret[0].(*Status) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchStatus indicates an expected call of FetchStatus. +func (mr *MockClientMockRecorder) FetchStatus(ctx any) *MockClientFetchStatusCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchStatus", reflect.TypeOf((*MockClient)(nil).FetchStatus), ctx) + return &MockClientFetchStatusCall{Call: call} +} + +// MockClientFetchStatusCall wrap *gomock.Call +type MockClientFetchStatusCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockClientFetchStatusCall) Return(arg0 *Status, arg1 error) *MockClientFetchStatusCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockClientFetchStatusCall) Do(f func(context.Context) (*Status, error)) *MockClientFetchStatusCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockClientFetchStatusCall) DoAndReturn(f func(context.Context) (*Status, error)) *MockClientFetchStatusCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/polygon/heimdall/metrics.go b/polygon/heimdall/metrics.go index 2fb58a3fcc6..7bfc2f9e4b0 100644 --- a/polygon/heimdall/metrics.go +++ b/polygon/heimdall/metrics.go @@ -34,6 +34,7 @@ type ( ) const ( + statusRequest requestType = "status" stateSyncRequest requestType = "state-sync" spanRequest requestType = "span" checkpointRequest requestType = "checkpoint" diff --git a/polygon/heimdall/service.go b/polygon/heimdall/service.go index dd60dc69bbf..4690e69c041 100644 --- a/polygon/heimdall/service.go +++ b/polygon/heimdall/service.go @@ -32,6 +32,10 @@ import ( "github.com/erigontech/erigon/polygon/bor/valset" ) +const ( + isCatchingDelaySec = 600 +) + type ServiceConfig struct { Store Store BorConfig *borcfg.BorConfig @@ -47,6 +51,7 @@ type Service struct { milestoneScraper *Scraper[*Milestone] spanScraper *Scraper[*Span] spanBlockProducersTracker *spanBlockProducersTracker + client Client ready ready } @@ -100,6 +105,7 @@ func NewService(config ServiceConfig) *Service { milestoneScraper: milestoneScraper, spanScraper: spanScraper, spanBlockProducersTracker: newSpanBlockProducersTracker(logger, borConfig, store.SpanBlockProducerSelections()), + client: client, } } @@ -397,3 +403,25 @@ func (s *Service) replayUntrackedSpans(ctx context.Context) error { return nil } + +func (s *Service) IsCatchingUp(ctx context.Context) (bool, error) { + status, err := s.client.FetchStatus(ctx) + if err != nil { + return false, err + } + + if status.CatchingUp { + return true, nil + } + + parsed, err := time.Parse(time.RFC3339, status.LatestBlockTime) + if err != nil { + return false, err + } + + if parsed.Unix() < time.Now().Unix()-isCatchingDelaySec { + return true, nil + } + + return false, nil +} diff --git a/polygon/heimdall/service_test.go b/polygon/heimdall/service_test.go index 9ac28693325..8ed65d6fd1d 100644 --- a/polygon/heimdall/service_test.go +++ b/polygon/heimdall/service_test.go @@ -489,3 +489,47 @@ type difficultiesKV struct { Signer common.Address Difficulty uint64 } + +func TestIsCatchingUp(t *testing.T) { + ctrl := gomock.NewController(t) + mockClient := NewMockClient(ctrl) + + s := Service{ + client: mockClient, + } + + mockClient.EXPECT(). + FetchStatus(gomock.Any()). + DoAndReturn(func(ctx context.Context) (*Status, error) { + return &Status{ + LatestBlockTime: "", + CatchingUp: true, + }, nil + }) + + isCatchingUp, err := s.IsCatchingUp(context.TODO()) + require.NoError(t, err) + require.True(t, isCatchingUp) +} + +func TestIsCatchingUpLateBlock(t *testing.T) { + ctrl := gomock.NewController(t) + mockClient := NewMockClient(ctrl) + + s := Service{ + client: mockClient, + } + + mockClient.EXPECT(). + FetchStatus(gomock.Any()). + DoAndReturn(func(ctx context.Context) (*Status, error) { + return &Status{ + LatestBlockTime: "2025-02-14T11:45:00.764588Z", + CatchingUp: false, + }, nil + }) + + isCatchingUp, err := s.IsCatchingUp(context.TODO()) + require.NoError(t, err) + require.True(t, isCatchingUp) +} diff --git a/polygon/heimdall/status.go b/polygon/heimdall/status.go new file mode 100644 index 00000000000..6e63fb5abca --- /dev/null +++ b/polygon/heimdall/status.go @@ -0,0 +1,30 @@ +// 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 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"` +} + +type StatusResponse struct { + Height string `json:"height"` + Result Status `json:"result"` +} diff --git a/polygon/sync/sync.go b/polygon/sync/sync.go index 5ac8ba471b2..93b67b948e9 100644 --- a/polygon/sync/sync.go +++ b/polygon/sync/sync.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/golang-lru/v2/simplelru" "github.com/erigontech/erigon-lib/common" + libcommon "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/eth/ethconfig" @@ -34,6 +35,7 @@ import ( ) 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) SynchronizeSpans(ctx context.Context, blockNum uint64) error @@ -668,6 +670,26 @@ func (s *Sync) Run(ctx context.Context) error { s.logger.Info(syncLogPrefix("running sync component")) + for { + // we have to check if the heimdall we are connected to is synchonised with the chain + // to prevent getting empty list of checkpoints/milestones during the sync + + catchingUp, err := s.heimdallSync.IsCatchingUp(ctx) + if err != nil { + return err + } + + if !catchingUp { + break + } + + s.logger.Warn(syncLogPrefix("your heimdalld process is behind, please check its logs and :1317/status api")) + + if err := libcommon.Sleep(ctx, 30*time.Second); err != nil { + return err + } + } + result, err := s.syncToTip(ctx) if err != nil { return err From 17a38cea92e53ffaef6ef106ba8599d96dc407cf Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Sat, 15 Feb 2025 17:09:01 +0100 Subject: [PATCH 02/42] qa-tests: schedule sync-with-externalcl on Sunday (#13820) --- .github/workflows/qa-sync-with-externalcl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/qa-sync-with-externalcl.yml b/.github/workflows/qa-sync-with-externalcl.yml index fb94b3a5bd8..2a95ab4e816 100644 --- a/.github/workflows/qa-sync-with-externalcl.yml +++ b/.github/workflows/qa-sync-with-externalcl.yml @@ -2,7 +2,7 @@ name: QA - Sync with external CL on: schedule: - - cron: '0 0 * * *' # Run every night at 00:00 AM UTC + - cron: '0 8 * * 0' # Run on Sunday at 08:00 AM UTC workflow_dispatch: # Run manually jobs: From 94446528128ce775608ac89ff4cadfa4976c7392 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Sat, 15 Feb 2025 17:09:42 +0100 Subject: [PATCH 03/42] qa-tests: improve rpc-integration tests (#13755) check for rpcdaemon startup failure, run tests one by one (not in parallel) --- .github/workflows/qa-rpc-integration-tests.yml | 16 +++++++++++++--- .github/workflows/scripts/run_rpc_tests.sh | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/qa-rpc-integration-tests.yml b/.github/workflows/qa-rpc-integration-tests.yml index 46c34cbabfe..c454d2dc275 100644 --- a/.github/workflows/qa-rpc-integration-tests.yml +++ b/.github/workflows/qa-rpc-integration-tests.yml @@ -54,13 +54,22 @@ jobs: - name: Run RpcDaemon working-directory: ${{ github.workspace }}/build/bin run: | - echo "RpcDaemon starting..." + echo "Starting RpcDaemon..." - ./rpcdaemon --datadir $ERIGON_REFERENCE_DATA_DIR --http.api admin,debug,eth,parity,erigon,trace,web3,txpool,ots,net --ws --verbosity 1 > erigon.log 2>&1 & + ./rpcdaemon --datadir $ERIGON_REFERENCE_DATA_DIR --http.api admin,debug,eth,parity,erigon,trace,web3,txpool,ots,net --ws > erigon.log 2>&1 & RPC_DAEMON_PID=$! + RPC_DAEMON_EXIT_STATUS=$? echo "RPC_DAEMON_PID=$RPC_DAEMON_PID" >> $GITHUB_ENV + sleep 5 + tail erigon.log + + if [ $RPC_DAEMON_EXIT_STATUS -ne 0 ]; then + echo "RpcDaemon failed to start" + echo "::error::Error detected during tests: RpcDaemon failed to start" + exit 1 + fi echo "RpcDaemon started" - name: Wait for port 8545 to be opened @@ -75,6 +84,7 @@ jobs: done if ! nc -z localhost 8545; then echo "Port 8545 did not open in time" + echo "::error::Error detected during tests: Port 8545 did not open in time" exit 1 fi @@ -153,6 +163,6 @@ jobs: - name: Action for Failure if: steps.test_step.outputs.TEST_RESULT != 'success' run: | - echo "::error::Error detected during tests" + echo "::error::Error detected during tests: some tests failed, check the logs or the artifacts for more details" exit 1 diff --git a/.github/workflows/scripts/run_rpc_tests.sh b/.github/workflows/scripts/run_rpc_tests.sh index 8dd21aca744..027eea7d74a 100755 --- a/.github/workflows/scripts/run_rpc_tests.sh +++ b/.github/workflows/scripts/run_rpc_tests.sh @@ -42,6 +42,6 @@ disabled_tests=( # Transform the array into a comma-separated string disabled_test_list=$(IFS=,; echo "${disabled_tests[*]}") -python3 ./run_tests.py -p 8545 --continue -f --json-diff -x "$disabled_test_list" +python3 ./run_tests.py -p 8545 --continue -f --json-diff --serial -x "$disabled_test_list" exit $? From be6dc84ce3a3632756bb85fa9cf205653eeebda8 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Sat, 15 Feb 2025 17:58:16 +0100 Subject: [PATCH 04/42] Caplin: update highest process slot correctly during `ForwardSync` (#13825) --- cl/phase1/stages/forward_sync.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cl/phase1/stages/forward_sync.go b/cl/phase1/stages/forward_sync.go index 0957a4b87a7..e7162b1df8d 100644 --- a/cl/phase1/stages/forward_sync.go +++ b/cl/phase1/stages/forward_sync.go @@ -144,7 +144,10 @@ func processDownloadedBlockBatches(ctx context.Context, logger log.Logger, cfg * if errors.Is(err, forkchoice.ErrEIP4844DataNotAvailable) { // Return an error if EIP-4844 data is not available logger.Trace("[Caplin] forward sync EIP-4844 data not available", "blockSlot", block.Block.Slot) - return highestBlockProcessed, nil + if newHighestBlockProcessed == 0 { + return 0, nil + } + return newHighestBlockProcessed - 1, nil } // Return an error if block processing fails err = fmt.Errorf("bad blocks segment received: %w", err) From 10e2e49564d30ea461c14df150e37decd69915b2 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Sun, 16 Feb 2025 23:12:29 +0100 Subject: [PATCH 05/42] Caplin: fix goddamn forward sync (#13831) --- cl/phase1/network/beacon_downloader.go | 15 +++++--- cl/phase1/stages/forward_sync.go | 51 +++++++++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/cl/phase1/network/beacon_downloader.go b/cl/phase1/network/beacon_downloader.go index 67626707b8a..64a537eaeea 100644 --- a/cl/phase1/network/beacon_downloader.go +++ b/cl/phase1/network/beacon_downloader.go @@ -76,7 +76,7 @@ type peerAndBlocks struct { } func (f *ForwardBeaconDownloader) RequestMore(ctx context.Context) { - count := uint64(32) + count := uint64(16) var atomicResp atomic.Value atomicResp.Store(peerAndBlocks{}) reqInterval := time.NewTicker(300 * time.Millisecond) @@ -96,11 +96,14 @@ Loop: } // double the request count every 10 seconds. This is inspired by the mekong network, which has many consecutive missing blocks. reqCount := count - if !f.highestSlotUpdateTime.IsZero() { - multiplier := int(time.Since(f.highestSlotUpdateTime).Seconds()) / 10 - multiplier = min(multiplier, 6) - reqCount *= uint64(1 << uint(multiplier)) - } + // NEED TO COMMENT THIS BC IT CAUSES ISSUES ON MAINNET + + // if !f.highestSlotUpdateTime.IsZero() { + // multiplier := int(time.Since(f.highestSlotUpdateTime).Seconds()) / 10 + // multiplier = min(multiplier, 6) + // reqCount *= uint64(1 << uint(multiplier)) + // } + // leave a warning if we are stuck for more than 90 seconds if time.Since(f.highestSlotUpdateTime) > 90*time.Second { log.Trace("Forward beacon downloader gets stuck", "time", time.Since(f.highestSlotUpdateTime).Seconds(), "highestSlotProcessed", f.highestSlotProcessed) diff --git a/cl/phase1/stages/forward_sync.go b/cl/phase1/stages/forward_sync.go index e7162b1df8d..bb611eaebd1 100644 --- a/cl/phase1/stages/forward_sync.go +++ b/cl/phase1/stages/forward_sync.go @@ -37,16 +37,18 @@ func shouldProcessBlobs(blocks []*cltypes.SignedBeaconBlock, cfg *Cfg) bool { } // Check if the requested blocks are too old to request blobs // https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/p2p-interface.md#the-reqresp-domain - highestEpoch := highestSlot / cfg.beaconCfg.SlotsPerEpoch - currentEpoch := cfg.ethClock.GetCurrentEpoch() - minEpochDist := uint64(0) - if currentEpoch > cfg.beaconCfg.MinEpochsForBlobSidecarsRequests { - minEpochDist = currentEpoch - cfg.beaconCfg.MinEpochsForBlobSidecarsRequests - } - finalizedEpoch := currentEpoch - 2 - if highestEpoch < max(cfg.beaconCfg.DenebForkEpoch, minEpochDist, finalizedEpoch) { - return false - } + + // this is bad + // highestEpoch := highestSlot / cfg.beaconCfg.SlotsPerEpoch + // currentEpoch := cfg.ethClock.GetCurrentEpoch() + // minEpochDist := uint64(0) + // if currentEpoch > cfg.beaconCfg.MinEpochsForBlobSidecarsRequests { + // minEpochDist = currentEpoch - cfg.beaconCfg.MinEpochsForBlobSidecarsRequests + // } + // finalizedEpoch := currentEpoch - 2 + // if highestEpoch < max(cfg.beaconCfg.DenebForkEpoch, minEpochDist, finalizedEpoch) { + // return false + // } return blobsExist } @@ -67,6 +69,7 @@ func downloadAndProcessEip4844DA(ctx context.Context, logger log.Logger, cfg *Cf err = fmt.Errorf("failed to get blob identifiers: %w", err) return } + // If there are no blobs to retrieve, return the highest slot processed if ids.Len() == 0 { return highestSlotProcessed, nil @@ -98,6 +101,30 @@ func downloadAndProcessEip4844DA(ctx context.Context, logger log.Logger, cfg *Cf return highestProcessed - 1, err } +func filterUnneededBlocks(ctx context.Context, blocks []*cltypes.SignedBeaconBlock, cfg *Cfg) []*cltypes.SignedBeaconBlock { + filtered := make([]*cltypes.SignedBeaconBlock, 0, len(blocks)) + // Find the latest block in the list + for _, block := range blocks { + blockRoot, err := block.Block.HashSSZ() + if err != nil { + panic(err) + } + _, hasInFcu := cfg.forkChoice.GetHeader(blockRoot) + + var hasSignedHeaderInDB bool + if err = cfg.indiciesDB.View(ctx, func(tx kv.Tx) error { + _, hasSignedHeaderInDB, err = beacon_indicies.ReadSignedHeaderByBlockRoot(ctx, tx, blockRoot) + return err + }); err != nil { + panic(err) + } + if !hasInFcu || !hasSignedHeaderInDB { + filtered = append(filtered, block) + } + } + return filtered +} + // processDownloadedBlockBatches processes a batch of downloaded blocks. // It takes the highest block processed, a flag to determine if insertion is needed, and a list of signed beacon blocks as input. // It returns the new highest block processed and an error if any. @@ -107,6 +134,9 @@ func processDownloadedBlockBatches(ctx context.Context, logger log.Logger, cfg * return blocks[i].Block.Slot < blocks[j].Block.Slot }) + // Filter out blocks that are already in the FCU or have a signed header in the DB + blocks = filterUnneededBlocks(ctx, blocks, cfg) + var ( blockRoot common.Hash st *state.CachingBeaconState @@ -141,6 +171,7 @@ func processDownloadedBlockBatches(ctx context.Context, logger log.Logger, cfg * // Process the block if err = processBlock(ctx, cfg, cfg.indiciesDB, block, false, true, true); err != nil { + fmt.Println("EIP-4844 data not available", err, block.Block.Slot) if errors.Is(err, forkchoice.ErrEIP4844DataNotAvailable) { // Return an error if EIP-4844 data is not available logger.Trace("[Caplin] forward sync EIP-4844 data not available", "blockSlot", block.Block.Slot) From 579dc632566d875d95828df9191949142d88ee95 Mon Sep 17 00:00:00 2001 From: battlmonstr Date: Mon, 17 Feb 2025 11:16:03 +0100 Subject: [PATCH 06/42] silkworm: add state snapshots (#13766) Previously the Silkworm library was capable of reading Erigon 2 "blocks" snapshots (.seg and .idx). The code to provide them to Silkworm was a part of RoSnapshots. The old code is moved to be a part of silkworm.SnapshotsRepository, and migrated to the updated Silkworm C API. The new code adds an ability to provide Erigon 3 "state" snapshots for reading by Silkworm during evmone execution mode (`--silkworm.exec`). It is based on the AggregatorRoTx.StaticFilesInRange method that was made public (with the related DTOs) to be accessible from the silkworm package. BtIndex.DataHandle() added to expose the memory mapped file pointer to Silkworm (in the same way as Decompressor and recsplit.Index). A bunch of public methods added to filesItem via FilesItem interface to expose the snapshot file objects to Silkworm. --- erigon-lib/state/aggregator.go | 6 +- erigon-lib/state/aggregator_files.go | 29 ++- erigon-lib/state/btree_index.go | 5 + erigon-lib/state/files_item.go | 17 ++ erigon-lib/state/inverted_index.go | 4 + erigon-lib/state/merge.go | 8 + erigon-lib/state/squeeze.go | 5 +- eth/stagedsync/stage_snapshots.go | 18 +- go.mod | 2 +- go.sum | 4 +- turbo/silkworm/silkworm.go | 24 +-- turbo/silkworm/snapshots_repository.go | 267 +++++++++++++++++++++++++ turbo/snapshotsync/snapshots.go | 76 +------ 13 files changed, 365 insertions(+), 100 deletions(-) create mode 100644 turbo/silkworm/snapshots_repository.go diff --git a/erigon-lib/state/aggregator.go b/erigon-lib/state/aggregator.go index 740456dd0f2..1b9cfeeccc3 100644 --- a/erigon-lib/state/aggregator.go +++ b/erigon-lib/state/aggregator.go @@ -692,7 +692,7 @@ func (a *Aggregator) mergeLoopStep(ctx context.Context, toTxNum uint64) (somethi return false, nil } - outs, err := aggTx.staticFilesInRange(r) + outs, err := aggTx.StaticFilesInRange(r) defer func() { if closeAll { outs.Close() @@ -1244,6 +1244,10 @@ type RangesV3 struct { invertedIndex []*MergeRange } +func NewRangesV3(domain [kv.DomainLen]DomainRanges, invertedIndex []*MergeRange) RangesV3 { + return RangesV3{domain: domain, invertedIndex: invertedIndex} +} + func (r RangesV3) String() string { ss := []string{} for _, d := range &r.domain { diff --git a/erigon-lib/state/aggregator_files.go b/erigon-lib/state/aggregator_files.go index b129d87cac1..c086d35d604 100644 --- a/erigon-lib/state/aggregator_files.go +++ b/erigon-lib/state/aggregator_files.go @@ -17,6 +17,7 @@ package state import ( + "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/kv" ) @@ -27,7 +28,23 @@ type SelectedStaticFilesV3 struct { ii [][]*filesItem } -func (sf SelectedStaticFilesV3) Close() { +func (sf *SelectedStaticFilesV3) DomainFiles(name kv.Domain) []FilesItem { + return common.SliceMap(sf.d[name], func(item *filesItem) FilesItem { return item }) +} + +func (sf *SelectedStaticFilesV3) DomainHistoryFiles(name kv.Domain) []FilesItem { + return common.SliceMap(sf.dHist[name], func(item *filesItem) FilesItem { return item }) +} + +func (sf *SelectedStaticFilesV3) DomainInvertedIndexFiles(name kv.Domain) []FilesItem { + return common.SliceMap(sf.dIdx[name], func(item *filesItem) FilesItem { return item }) +} + +func (sf *SelectedStaticFilesV3) InvertedIndexFiles(id int) []FilesItem { + return common.SliceMap(sf.ii[id], func(item *filesItem) FilesItem { return item }) +} + +func (sf *SelectedStaticFilesV3) Close() { clist := make([][]*filesItem, 0, int(kv.DomainLen)+len(sf.ii)) for id := range sf.d { clist = append(clist, sf.d[id], sf.dIdx[id], sf.dHist[id]) @@ -48,7 +65,7 @@ func (sf SelectedStaticFilesV3) Close() { } } -func (ac *AggregatorRoTx) staticFilesInRange(r *RangesV3) (*SelectedStaticFilesV3, error) { +func (ac *AggregatorRoTx) StaticFilesInRange(r *RangesV3) (*SelectedStaticFilesV3, error) { sf := &SelectedStaticFilesV3{ii: make([][]*filesItem, len(r.invertedIndex))} for id := range ac.d { if !r.domain[id].any() { @@ -65,6 +82,14 @@ func (ac *AggregatorRoTx) staticFilesInRange(r *RangesV3) (*SelectedStaticFilesV return sf, nil } +func (ac *AggregatorRoTx) InvertedIndicesLen() int { + return len(ac.iis) +} + +func (ac *AggregatorRoTx) InvertedIndexName(id int) kv.InvertedIdx { + return ac.iis[id].name +} + type MergedFilesV3 struct { d [kv.DomainLen]*filesItem dHist [kv.DomainLen]*filesItem diff --git a/erigon-lib/state/btree_index.go b/erigon-lib/state/btree_index.go index 72906690bfa..4e359af10e6 100644 --- a/erigon-lib/state/btree_index.go +++ b/erigon-lib/state/btree_index.go @@ -30,6 +30,7 @@ import ( "strings" "sync" "time" + "unsafe" "github.com/c2h5oh/datasize" "github.com/edsrzf/mmap-go" @@ -994,6 +995,10 @@ func (b *BtIndex) newCursor(k, v []byte, d uint64, g *seg.Reader) *Cursor { return c } +func (b *BtIndex) DataHandle() unsafe.Pointer { + return unsafe.Pointer(&b.data[0]) +} + func (b *BtIndex) Size() int64 { return b.size } func (b *BtIndex) ModTime() time.Time { return b.modTime } diff --git a/erigon-lib/state/files_item.go b/erigon-lib/state/files_item.go index 093836f609b..0c81513781a 100644 --- a/erigon-lib/state/files_item.go +++ b/erigon-lib/state/files_item.go @@ -62,6 +62,15 @@ type filesItem struct { canDelete atomic.Bool } +type FilesItem interface { + Segment() *seg.Decompressor + AccessorIndex() *recsplit.Index + BtIndex() *BtIndex + ExistenceFilter() *ExistenceFilter +} + +var _ FilesItem = (*filesItem)(nil) + func newFilesItem(startTxNum, endTxNum, stepSize uint64) *filesItem { startStep := startTxNum / stepSize endStep := endTxNum / stepSize @@ -69,6 +78,14 @@ func newFilesItem(startTxNum, endTxNum, stepSize uint64) *filesItem { return &filesItem{startTxNum: startTxNum, endTxNum: endTxNum, frozen: frozen} } +func (i *filesItem) Segment() *seg.Decompressor { return i.decompressor } + +func (i *filesItem) AccessorIndex() *recsplit.Index { return i.index } + +func (i *filesItem) BtIndex() *BtIndex { return i.bindex } + +func (i *filesItem) ExistenceFilter() *ExistenceFilter { return i.existence } + // isSubsetOf - when `j` covers `i` but not equal `i` func (i *filesItem) isSubsetOf(j *filesItem) bool { return (j.startTxNum <= i.startTxNum && i.endTxNum <= j.endTxNum) && (j.startTxNum != i.startTxNum || i.endTxNum != j.endTxNum) diff --git a/erigon-lib/state/inverted_index.go b/erigon-lib/state/inverted_index.go index 6310755f77c..592f1665726 100644 --- a/erigon-lib/state/inverted_index.go +++ b/erigon-lib/state/inverted_index.go @@ -506,6 +506,10 @@ type MergeRange struct { to uint64 } +func NewMergeRange(name string, needMerge bool, from uint64, to uint64) *MergeRange { + return &MergeRange{name: name, needMerge: needMerge, from: from, to: to} +} + func (mr *MergeRange) FromTo() (uint64, uint64) { return mr.from, mr.to } diff --git a/erigon-lib/state/merge.go b/erigon-lib/state/merge.go index 6d5c0f06f8b..e73564c4521 100644 --- a/erigon-lib/state/merge.go +++ b/erigon-lib/state/merge.go @@ -81,6 +81,10 @@ type DomainRanges struct { aggStep uint64 } +func NewDomainRanges(name kv.Domain, values MergeRange, history HistoryRanges, aggStep uint64) DomainRanges { + return DomainRanges{name: name, values: values, history: history, aggStep: aggStep} +} + func (r DomainRanges) String() string { var b strings.Builder if r.values.needMerge { @@ -219,6 +223,10 @@ type HistoryRanges struct { index MergeRange } +func NewHistoryRanges(history MergeRange, index MergeRange) HistoryRanges { + return HistoryRanges{history: history, index: index} +} + func (r HistoryRanges) String(aggStep uint64) string { var str string if r.history.needMerge { diff --git a/erigon-lib/state/squeeze.go b/erigon-lib/state/squeeze.go index 73b11e34f63..69500751115 100644 --- a/erigon-lib/state/squeeze.go +++ b/erigon-lib/state/squeeze.go @@ -13,6 +13,7 @@ import ( "time" "github.com/c2h5oh/datasize" + "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/common/datadir" "github.com/erigontech/erigon-lib/common/dir" @@ -132,7 +133,7 @@ func (ac *AggregatorRoTx) SqueezeCommitmentFiles() error { }, }, } - sf, err := ac.staticFilesInRange(rng) + sf, err := ac.StaticFilesInRange(rng) if err != nil { return err } @@ -331,7 +332,7 @@ func (a *Aggregator) RebuildCommitmentFiles(ctx context.Context, rwDb kv.RwDB, t }, }, } - sf, err := acRo.staticFilesInRange(rng) + sf, err := acRo.StaticFilesInRange(rng) if err != nil { return nil, err } diff --git a/eth/stagedsync/stage_snapshots.go b/eth/stagedsync/stage_snapshots.go index f6c91ce99b5..26175a45283 100644 --- a/eth/stagedsync/stage_snapshots.go +++ b/eth/stagedsync/stage_snapshots.go @@ -300,12 +300,6 @@ func DownloadAndIndexSnapshotsIfNeed(s *StageState, ctx context.Context, tx kv.R return err } - if cfg.silkworm != nil { - if err := cfg.blockReader.Snapshots().(silkworm.CanAddSnapshotsToSilkwarm).AddSnapshotsToSilkworm(cfg.silkworm); err != nil { - return err - } - } - indexWorkers := estimate.IndexSnapshot.Workers() diagnostics.Send(diagnostics.CurrentSyncSubStage{SubStage: "E3 Indexing"}) if err := agg.BuildMissedIndices(ctx, indexWorkers); err != nil { @@ -321,6 +315,18 @@ func DownloadAndIndexSnapshotsIfNeed(s *StageState, ctx context.Context, tx kv.R cfg.notifier.Events.OnNewSnapshot() } + if cfg.silkworm != nil { + repository := silkworm.NewSnapshotsRepository( + cfg.silkworm, + cfg.blockReader.Snapshots().(*freezeblocks.RoSnapshots), + agg, + logger, + ) + if err := repository.Update(); err != nil { + return err + } + } + frozenBlocks := cfg.blockReader.FrozenBlocks() if s.BlockNumber < frozenBlocks { // allow genesis if err := s.Update(tx, frozenBlocks); err != nil { diff --git a/go.mod b/go.mod index 7ff9ca2d42b..96d126ca531 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/erigontech/erigonwatch v0.0.0-20240718131902-b6576bde1116 github.com/erigontech/mdbx-go v0.38.6-0.20250205222432-e4dd01978d7f github.com/erigontech/secp256k1 v1.1.0 - github.com/erigontech/silkworm-go v0.18.0 + github.com/erigontech/silkworm-go v0.24.0 ) require ( diff --git a/go.sum b/go.sum index 39dbe483f81..93519c3131e 100644 --- a/go.sum +++ b/go.sum @@ -275,8 +275,8 @@ github.com/erigontech/mdbx-go v0.38.6-0.20250205222432-e4dd01978d7f h1:2uZv1SGd7 github.com/erigontech/mdbx-go v0.38.6-0.20250205222432-e4dd01978d7f/go.mod h1:lkqHAZqXtFaIPlvTaGAx3VUDuGYZcuhve1l4JVVN1Z0= github.com/erigontech/secp256k1 v1.1.0 h1:mO3YJMUSoASE15Ya//SoHiisptUhdXExuMUN1M0X9qY= github.com/erigontech/secp256k1 v1.1.0/go.mod h1:GokhPepsMB+EYDs7I5JZCprxHW6+yfOcJKaKtoZ+Fls= -github.com/erigontech/silkworm-go v0.18.0 h1:j56p61xZHBFhZGH1OixlGU8KcfjHzcw9pjAfjmVsOZA= -github.com/erigontech/silkworm-go v0.18.0/go.mod h1:O50ux0apICEVEGyRWiE488K8qz8lc3PA/SXbQQAc8SU= +github.com/erigontech/silkworm-go v0.24.0 h1:fFe74CjQM5LI7ouMYjmqfFaqIFzQTpMrt+ls+a5PxpE= +github.com/erigontech/silkworm-go v0.24.0/go.mod h1:O50ux0apICEVEGyRWiE488K8qz8lc3PA/SXbQQAc8SU= github.com/erigontech/speedtest v0.0.2 h1:W9Cvky/8AMUtUONwkLA/dZjeQ2XfkBdYfJzvhMZUO+U= github.com/erigontech/speedtest v0.0.2/go.mod h1:vulsRNiM51BmSTbVtch4FWxKxx53pS2D35lZTtao0bw= github.com/erigontech/torrent v1.54.3-alpha-1 h1:oyT9YpMr82g566v0STVKW0ZTdX/eun03cW2mKmKuTAQ= diff --git a/turbo/silkworm/silkworm.go b/turbo/silkworm/silkworm.go index 385da6ae36f..85427047bbe 100644 --- a/turbo/silkworm/silkworm.go +++ b/turbo/silkworm/silkworm.go @@ -33,16 +33,18 @@ type SilkwormLogLevel = silkworm_go.SilkwormLogLevel type SentrySettings = silkworm_go.SentrySettings type RpcDaemonSettings = silkworm_go.RpcDaemonSettings type RpcInterfaceLogSettings = silkworm_go.RpcInterfaceLogSettings -type MappedHeaderSnapshot = silkworm_go.MappedHeaderSnapshot -type MappedBodySnapshot = silkworm_go.MappedBodySnapshot -type MappedTxnSnapshot = silkworm_go.MappedTxnSnapshot -type MappedChainSnapshot = silkworm_go.MappedChainSnapshot - -var NewMemoryMappedRegion = silkworm_go.NewMemoryMappedRegion -var NewMappedHeaderSnapshot = silkworm_go.NewMappedHeaderSnapshot -var NewMappedBodySnapshot = silkworm_go.NewMappedBodySnapshot -var NewMappedTxnSnapshot = silkworm_go.NewMappedTxnSnapshot +type HeadersSnapshot = silkworm_go.HeadersSnapshot +type BodiesSnapshot = silkworm_go.BodiesSnapshot +type TransactionsSnapshot = silkworm_go.TransactionsSnapshot +type BlocksSnapshotBundle = silkworm_go.BlocksSnapshotBundle +type InvertedIndexSnapshot = silkworm_go.InvertedIndexSnapshot +type HistorySnapshot = silkworm_go.HistorySnapshot +type DomainSnapshot = silkworm_go.DomainSnapshot +type StateSnapshotBundleLatest = silkworm_go.StateSnapshotBundleLatest +type StateSnapshotBundleHistorical = silkworm_go.StateSnapshotBundleHistorical + +var NewFilePath = silkworm_go.NewFilePath var ErrInterrupted = silkworm_go.ErrInterrupted func New(dataDirPath string, libMdbxVersion string, numIOContexts uint32, logLevel log.Lvl) (*Silkworm, error) { @@ -125,7 +127,3 @@ func ExecuteBlocksPerpetual(s *Silkworm, db kv.RwDB, chainID *big.Int, startBloc } return lastExecutedBlock, err } - -type CanAddSnapshotsToSilkwarm interface { - AddSnapshotsToSilkworm(*Silkworm) error -} diff --git a/turbo/silkworm/snapshots_repository.go b/turbo/silkworm/snapshots_repository.go new file mode 100644 index 00000000000..9c06850ea0e --- /dev/null +++ b/turbo/silkworm/snapshots_repository.go @@ -0,0 +1,267 @@ +package silkworm + +import ( + "errors" + "math" + "time" + "unsafe" + + silkworm_go "github.com/erigontech/silkworm-go" + + "github.com/erigontech/erigon-lib/kv" + "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon-lib/recsplit" + "github.com/erigontech/erigon-lib/seg" + "github.com/erigontech/erigon-lib/state" + coresnaptype "github.com/erigontech/erigon/core/snaptype" + "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" +) + +type SnapshotsRepository struct { + silkworm *Silkworm + + blockSnapshots *freezeblocks.RoSnapshots + stateSnapshots *state.Aggregator + + logger log.Logger +} + +func NewSnapshotsRepository( + silkworm *Silkworm, + blockSnapshots *freezeblocks.RoSnapshots, + stateSnapshots *state.Aggregator, + logger log.Logger, +) *SnapshotsRepository { + return &SnapshotsRepository{ + silkworm, + blockSnapshots, + stateSnapshots, + logger, + } +} + +type MemoryMappedFile interface { + FilePath() string + DataHandle() unsafe.Pointer + Size() int64 +} + +var _ MemoryMappedFile = (*seg.Decompressor)(nil) +var _ MemoryMappedFile = (*recsplit.Index)(nil) +var _ MemoryMappedFile = (*state.BtIndex)(nil) + +func memoryMappedFile(file MemoryMappedFile) silkworm_go.MemoryMappedFile { + return silkworm_go.MemoryMappedFile{ + FilePath: NewFilePath(file.FilePath()), + DataHandle: file.DataHandle(), + Size: file.Size(), + } +} + +func (r *SnapshotsRepository) Update() error { + startTime := time.Now() + r.logger.Debug("[silkworm] snapshots updating...") + + blocksView := r.blockSnapshots.View() + defer blocksView.Close() + err := r.updateBlocks(blocksView) + if err != nil { + return err + } + + stateTx := r.stateSnapshots.BeginFilesRo() + defer stateTx.Close() + err = r.updateState(stateTx) + + if err == nil { + duration := time.Since(startTime) + if duration > 10*time.Second { + r.logger.Info("[silkworm] snapshots updated", "duration", duration) + } else { + r.logger.Debug("[silkworm] snapshots updated", "duration", duration) + } + } + return err +} + +func (r *SnapshotsRepository) updateBlocks(view *freezeblocks.View) error { + segmentsHeaders := view.Headers() + segmentsBodies := view.Bodies() + segmentsTransactions := view.Txs() + + count := len(segmentsHeaders) + if (len(segmentsBodies) != count) || (len(segmentsTransactions) != count) { + return errors.New("silkworm.SnapshotsRepository.updateBlocks: the number of headers/bodies/transactions segments must be the same") + } + + startTime := time.Now() + for i := 0; i < count; i++ { + r.logger.Trace("[silkworm] snapshots updating blocks", "i", i, "count", count) + segmentHeaders := segmentsHeaders[i].Src() + segmentBodies := segmentsBodies[i].Src() + segmentTransactions := segmentsTransactions[i].Src() + + err := r.silkworm.AddBlocksSnapshotBundle(BlocksSnapshotBundle{ + Headers: HeadersSnapshot{ + Segment: memoryMappedFile(segmentHeaders), + HeaderHashIndex: memoryMappedFile(segmentHeaders.Index()), + }, + Bodies: BodiesSnapshot{ + Segment: memoryMappedFile(segmentBodies), + BlockNumIndex: memoryMappedFile(segmentBodies.Index()), + }, + Transactions: TransactionsSnapshot{ + Segment: memoryMappedFile(segmentTransactions), + TxnHashIndex: memoryMappedFile(segmentTransactions.Index(coresnaptype.Indexes.TxnHash)), + TxnHash2BlockIndex: memoryMappedFile(segmentTransactions.Index(coresnaptype.Indexes.TxnHash2BlockNum)), + }, + }) + if err != nil { + return err + } + } + r.logger.Debug("[silkworm] snapshots updated blocks", "count", count, "duration", time.Since(startTime)) + + return nil +} + +func makeInvertedIndexSnapshot(item state.FilesItem) InvertedIndexSnapshot { + return InvertedIndexSnapshot{ + Segment: memoryMappedFile(item.Segment()), + AccessorIndex: memoryMappedFile(item.AccessorIndex()), + } +} + +func makeHistorySnapshot(historyItem state.FilesItem, iiItem state.FilesItem) HistorySnapshot { + return HistorySnapshot{ + Segment: memoryMappedFile(historyItem.Segment()), + AccessorIndex: memoryMappedFile(historyItem.AccessorIndex()), + InvertedIndex: makeInvertedIndexSnapshot(iiItem), + } +} + +func makeDomainSnapshot(item state.FilesItem) DomainSnapshot { + var accessorIndexOpt *silkworm_go.MemoryMappedFile + if item.AccessorIndex() != nil { + accessorIndex := memoryMappedFile(item.AccessorIndex()) + accessorIndexOpt = &accessorIndex + } + return DomainSnapshot{ + Segment: memoryMappedFile(item.Segment()), + ExistenceIndex: silkworm_go.MemoryMappedFile{ + FilePath: NewFilePath(item.ExistenceFilter().FilePath), + DataHandle: nil, + Size: 0, + }, + BTreeIndex: memoryMappedFile(item.BtIndex()), + AccessorIndex: accessorIndexOpt, + } +} + +func (r *SnapshotsRepository) updateState(stateTx *state.AggregatorRoTx) error { + mergeRange := state.NewMergeRange("", true, 0, math.MaxUint64) + domainRanges := func(name kv.Domain) state.DomainRanges { + return state.NewDomainRanges( + name, + *mergeRange, + state.NewHistoryRanges(*mergeRange, *mergeRange), + 0, + ) + } + var allDomainRanges [kv.DomainLen]state.DomainRanges + for i := 0; i < len(allDomainRanges); i++ { + allDomainRanges[i] = domainRanges(kv.Domain(i)) + } + iiRanges := make([]*state.MergeRange, stateTx.InvertedIndicesLen()) + for i := 0; i < len(iiRanges); i++ { + iiRanges[i] = mergeRange + } + ranges := state.NewRangesV3(allDomainRanges, iiRanges) + + allFiles, err := stateTx.StaticFilesInRange(&ranges) + if err != nil { + return err + } + + iiNames := make(map[kv.InvertedIdx]int, len(iiRanges)) + for i := 0; i < len(iiRanges); i++ { + iiNames[stateTx.InvertedIndexName(i)] = i + } + + iiFilesLogAddresses := allFiles.InvertedIndexFiles(iiNames[kv.LogAddrIdx]) + iiFilesLogTopics := allFiles.InvertedIndexFiles(iiNames[kv.LogTopicIdx]) + iiFilesTracesFrom := allFiles.InvertedIndexFiles(iiNames[kv.TracesFromIdx]) + iiFilesTracesTo := allFiles.InvertedIndexFiles(iiNames[kv.TracesToIdx]) + + historyFilesAccounts := allFiles.DomainHistoryFiles(kv.AccountsDomain) + historyFilesStorage := allFiles.DomainHistoryFiles(kv.StorageDomain) + historyFilesCode := allFiles.DomainHistoryFiles(kv.CodeDomain) + historyFilesReceipts := allFiles.DomainHistoryFiles(kv.ReceiptDomain) + + historyIIFilesAccounts := allFiles.DomainInvertedIndexFiles(kv.AccountsDomain) + historyIIFilesStorage := allFiles.DomainInvertedIndexFiles(kv.StorageDomain) + historyIIFilesCode := allFiles.DomainInvertedIndexFiles(kv.CodeDomain) + historyIIFilesReceipts := allFiles.DomainInvertedIndexFiles(kv.ReceiptDomain) + + domainFilesAccounts := allFiles.DomainFiles(kv.AccountsDomain) + domainFilesStorage := allFiles.DomainFiles(kv.StorageDomain) + domainFilesCode := allFiles.DomainFiles(kv.CodeDomain) + // TODO: enable after fixing .kvi configuration + // domainFilesCommitment := allFiles.DomainFiles(kv.CommitmentDomain) + domainFilesReceipts := allFiles.DomainFiles(kv.ReceiptDomain) + + countHistorical := len(iiFilesLogAddresses) + if (len(iiFilesLogTopics) != countHistorical) || (len(iiFilesTracesFrom) != countHistorical) || (len(iiFilesTracesTo) != countHistorical) || + (len(historyFilesAccounts) != countHistorical) || (len(historyFilesStorage) != countHistorical) || (len(historyFilesCode) != countHistorical) || (len(historyFilesReceipts) != countHistorical) || + (len(historyIIFilesAccounts) != countHistorical) || (len(historyIIFilesStorage) != countHistorical) || (len(historyIIFilesCode) != countHistorical) || (len(historyIIFilesReceipts) != countHistorical) { + return errors.New("silkworm.SnapshotsRepository.updateState: the number of historical files must be the same") + } + + startTimeHistorical := time.Now() + for i := 0; i < countHistorical; i++ { + r.logger.Trace("[silkworm] snapshots updating historical", "i", i, "count", countHistorical) + err := r.silkworm.AddStateSnapshotBundleHistorical(StateSnapshotBundleHistorical{ + Accounts: makeHistorySnapshot(historyFilesAccounts[i], historyIIFilesAccounts[i]), + Storage: makeHistorySnapshot(historyFilesStorage[i], historyIIFilesStorage[i]), + Code: makeHistorySnapshot(historyFilesCode[i], historyIIFilesCode[i]), + Receipts: makeHistorySnapshot(historyFilesReceipts[i], historyIIFilesReceipts[i]), + + LogAddresses: makeInvertedIndexSnapshot(iiFilesLogAddresses[i]), + LogTopics: makeInvertedIndexSnapshot(iiFilesLogTopics[i]), + TracesFrom: makeInvertedIndexSnapshot(iiFilesTracesFrom[i]), + TracesTo: makeInvertedIndexSnapshot(iiFilesTracesTo[i]), + }) + if err != nil { + return err + } + } + r.logger.Debug("[silkworm] snapshots updated historical", "count", countHistorical, "duration", time.Since(startTimeHistorical)) + + countLatest := len(domainFilesAccounts) + if (len(domainFilesStorage) != countLatest) || (len(domainFilesCode) != countLatest) || + /* TODO: enable after fixing .kvi configuration */ + /* (len(domainFilesCommitment) != countLatest) || */ + (len(domainFilesReceipts) != countLatest) { + return errors.New("silkworm.SnapshotsRepository.updateState: the number of latest files must be the same") + } + + startTimeLatest := time.Now() + for i := 0; i < countLatest; i++ { + r.logger.Trace("[silkworm] snapshots updating latest", "i", i, "count", countLatest) + err := r.silkworm.AddStateSnapshotBundleLatest(StateSnapshotBundleLatest{ + Accounts: makeDomainSnapshot(domainFilesAccounts[i]), + Storage: makeDomainSnapshot(domainFilesStorage[i]), + Code: makeDomainSnapshot(domainFilesCode[i]), + /* TODO: enable after fixing .kvi configuration */ + /* Commitment: makeDomainSnapshot(domainFilesCommitment[i]), */ + Commitment: makeDomainSnapshot(domainFilesReceipts[i]), + Receipts: makeDomainSnapshot(domainFilesReceipts[i]), + }) + if err != nil { + return err + } + } + r.logger.Debug("[silkworm] snapshots updated latest", "count", countLatest, "duration", time.Since(startTimeLatest)) + + return nil +} diff --git a/turbo/snapshotsync/snapshots.go b/turbo/snapshotsync/snapshots.go index 0d701468ff3..fd522c5f0e6 100644 --- a/turbo/snapshotsync/snapshots.go +++ b/turbo/snapshotsync/snapshots.go @@ -29,6 +29,9 @@ import ( "sync/atomic" "time" + "github.com/tidwall/btree" + "golang.org/x/sync/errgroup" + "github.com/erigontech/erigon-lib/chain" "github.com/erigontech/erigon-lib/chain/snapcfg" common2 "github.com/erigontech/erigon-lib/common" @@ -43,9 +46,6 @@ import ( coresnaptype "github.com/erigontech/erigon/core/snaptype" "github.com/erigontech/erigon/eth/ethconfig" "github.com/erigontech/erigon/eth/ethconfig/estimate" - "github.com/erigontech/erigon/turbo/silkworm" - "github.com/tidwall/btree" - "golang.org/x/sync/errgroup" ) type SortedRange interface { @@ -1450,76 +1450,6 @@ func (s *RoSnapshots) PrintDebug() { } } -func mappedHeaderSnapshot(sn *DirtySegment) *silkworm.MappedHeaderSnapshot { - segmentRegion := silkworm.NewMemoryMappedRegion(sn.FilePath(), sn.DataHandle(), sn.Size()) - idxRegion := silkworm.NewMemoryMappedRegion(sn.Index().FilePath(), sn.Index().DataHandle(), sn.Index().Size()) - return silkworm.NewMappedHeaderSnapshot(segmentRegion, idxRegion) -} - -func mappedBodySnapshot(sn *DirtySegment) *silkworm.MappedBodySnapshot { - segmentRegion := silkworm.NewMemoryMappedRegion(sn.FilePath(), sn.DataHandle(), sn.Size()) - idxRegion := silkworm.NewMemoryMappedRegion(sn.Index().FilePath(), sn.Index().DataHandle(), sn.Index().Size()) - return silkworm.NewMappedBodySnapshot(segmentRegion, idxRegion) -} - -func mappedTxnSnapshot(sn *DirtySegment) *silkworm.MappedTxnSnapshot { - segmentRegion := silkworm.NewMemoryMappedRegion(sn.FilePath(), sn.DataHandle(), sn.Size()) - idxTxnHash := sn.Index(coresnaptype.Indexes.TxnHash) - idxTxnHashRegion := silkworm.NewMemoryMappedRegion(idxTxnHash.FilePath(), idxTxnHash.DataHandle(), idxTxnHash.Size()) - idxTxnHash2BlockNum := sn.Index(coresnaptype.Indexes.TxnHash2BlockNum) - idxTxnHash2BlockRegion := silkworm.NewMemoryMappedRegion(idxTxnHash2BlockNum.FilePath(), idxTxnHash2BlockNum.DataHandle(), idxTxnHash2BlockNum.Size()) - return silkworm.NewMappedTxnSnapshot(segmentRegion, idxTxnHashRegion, idxTxnHash2BlockRegion) -} - -func (s *RoSnapshots) AddSnapshotsToSilkworm(silkwormInstance *silkworm.Silkworm) error { - v := s.View() - defer v.Close() - - s.visibleLock.RLock() - defer s.visibleLock.RUnlock() - - mappedHeaderSnapshots := make([]*silkworm.MappedHeaderSnapshot, 0) - if vis := v.segments[coresnaptype.Enums.Headers]; vis != nil { - for _, headerSegment := range vis.Segments { - mappedHeaderSnapshots = append(mappedHeaderSnapshots, mappedHeaderSnapshot(headerSegment.src)) - } - } - - mappedBodySnapshots := make([]*silkworm.MappedBodySnapshot, 0) - if vis := v.segments[coresnaptype.Enums.Bodies]; vis != nil { - for _, bodySegment := range vis.Segments { - mappedBodySnapshots = append(mappedBodySnapshots, mappedBodySnapshot(bodySegment.src)) - } - return nil - - } - - mappedTxnSnapshots := make([]*silkworm.MappedTxnSnapshot, 0) - if txs := v.segments[coresnaptype.Enums.Transactions]; txs != nil { - for _, txnSegment := range txs.Segments { - mappedTxnSnapshots = append(mappedTxnSnapshots, mappedTxnSnapshot(txnSegment.src)) - } - } - - if len(mappedHeaderSnapshots) != len(mappedBodySnapshots) || len(mappedBodySnapshots) != len(mappedTxnSnapshots) { - return errors.New("addSnapshots: the number of headers/bodies/txs snapshots must be the same") - } - - for i := 0; i < len(mappedHeaderSnapshots); i++ { - mappedSnapshot := &silkworm.MappedChainSnapshot{ - Headers: mappedHeaderSnapshots[i], - Bodies: mappedBodySnapshots[i], - Txs: mappedTxnSnapshots[i], - } - err := silkwormInstance.AddSnapshot(mappedSnapshot) - if err != nil { - return err - } - } - - return nil -} - type View struct { s *RoSnapshots segments []*RoTx From b4145263c79e29faad1c14af17efc02073dad739 Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:21:21 +0100 Subject: [PATCH 07/42] Engine API: shorter waits (#13839) Cherry pick PR #13821 into `main`. See Issue #13773 --- turbo/engineapi/engine_server.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/turbo/engineapi/engine_server.go b/turbo/engineapi/engine_server.go index b751e1fb124..c4f39d7817f 100644 --- a/turbo/engineapi/engine_server.go +++ b/turbo/engineapi/engine_server.go @@ -437,14 +437,14 @@ func (s *EngineServer) getQuickPayloadStatusIfPossible(ctx context.Context, bloc if header != nil && isCanonical { return &engine_types.PayloadStatus{Status: engine_types.ValidStatus, LatestValidHash: &blockHash}, nil } - if shouldWait, _ := waitForStuff(func() (bool, error) { + if shouldWait, _ := waitForStuff(50*time.Millisecond, func() (bool, error) { return parent == nil && s.hd.PosStatus() == headerdownload.Syncing, nil }); shouldWait { s.logger.Info(fmt.Sprintf("[%s] Downloading some other PoS blocks", prefix), "hash", blockHash) return &engine_types.PayloadStatus{Status: engine_types.SyncingStatus}, nil } } else { - if shouldWait, _ := waitForStuff(func() (bool, error) { + if shouldWait, _ := waitForStuff(50*time.Millisecond, func() (bool, error) { return header == nil && s.hd.PosStatus() == headerdownload.Syncing, nil }); shouldWait { s.logger.Info(fmt.Sprintf("[%s] Downloading some other PoS stuff", prefix), "hash", blockHash) @@ -458,7 +458,7 @@ func (s *EngineServer) getQuickPayloadStatusIfPossible(ctx context.Context, bloc return &engine_types.PayloadStatus{Status: engine_types.ValidStatus, LatestValidHash: &blockHash}, nil } } - waitingForExecutionReady, err := waitForStuff(func() (bool, error) { + waitingForExecutionReady, err := waitForStuff(500*time.Millisecond, func() (bool, error) { isReady, err := s.chainRW.Ready(ctx) return !isReady, err }) @@ -623,7 +623,7 @@ func (s *EngineServer) forkchoiceUpdated(ctx context.Context, forkchoiceState *e var resp *execution.AssembleBlockResponse - execBusy, err := waitForStuff(func() (bool, error) { + execBusy, err := waitForStuff(500*time.Millisecond, func() (bool, error) { resp, err = s.executionService.AssembleBlock(ctx, req) if err != nil { return false, err @@ -753,15 +753,9 @@ func (e *EngineServer) HandleNewPayload( if currentHeadNumber != nil { // We try waiting until we finish downloading the PoS blocks if the distance from the head is enough, // so that we will perform full validation. - success := false - for i := 0; i < 100; i++ { - time.Sleep(10 * time.Millisecond) - if e.blockDownloader.Status() == headerdownload.Synced { - success = true - break - } - } - if !success { + if stillSyncing, _ := waitForStuff(500*time.Millisecond, func() (bool, error) { + return e.blockDownloader.Status() != headerdownload.Synced, nil + }); stillSyncing { return &engine_types.PayloadStatus{Status: engine_types.SyncingStatus}, nil } status, _, latestValidHash, err := e.chainRW.ValidateChain(ctx, headerHash, headerNumber) @@ -891,15 +885,15 @@ func (e *EngineServer) SetConsuming(consuming bool) { e.consuming.Store(consuming) } -func waitForStuff(waitCondnF func() (bool, error)) (bool, error) { +func waitForStuff(maxWait time.Duration, waitCondnF func() (bool, error)) (bool, error) { shouldWait, err := waitCondnF() if err != nil || !shouldWait { return false, err } - // Times out after 8s - loosely based on timeouts of FCU and NewPayload for Ethereum specs - // Look for "timeout" in, for instance, https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md - for i := 0; i < 800; i++ { - time.Sleep(10 * time.Millisecond) + checkInterval := 10 * time.Millisecond + maxChecks := int64(maxWait) / int64(checkInterval) + for i := int64(0); i < maxChecks; i++ { + time.Sleep(checkInterval) shouldWait, err = waitCondnF() if err != nil || !shouldWait { return shouldWait, err From 1c7a9024c9d856ca076f990296e2cf954c2b6199 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Mon, 17 Feb 2025 13:16:09 +0100 Subject: [PATCH 08/42] Caplin: fix nil ptr (#13841) --- cl/phase1/stages/forward_sync.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cl/phase1/stages/forward_sync.go b/cl/phase1/stages/forward_sync.go index bb611eaebd1..7bde4554c59 100644 --- a/cl/phase1/stages/forward_sync.go +++ b/cl/phase1/stages/forward_sync.go @@ -136,6 +136,9 @@ func processDownloadedBlockBatches(ctx context.Context, logger log.Logger, cfg * // Filter out blocks that are already in the FCU or have a signed header in the DB blocks = filterUnneededBlocks(ctx, blocks, cfg) + if len(blocks) == 0 { + return highestBlockProcessed, nil + } var ( blockRoot common.Hash From efdd02acf2db370aa50aa88098ee1acadd2f96cc Mon Sep 17 00:00:00 2001 From: Ostroukhov Nikita Date: Mon, 17 Feb 2025 15:51:49 +0000 Subject: [PATCH 09/42] Fixed polygon sync when no heimdall waypoints are available (#13844) --- polygon/heimdall/scraper.go | 15 +---- polygon/heimdall/service.go | 10 ++- polygon/heimdall/service_test.go | 8 ++- polygon/sync/sync.go | 105 ++++++++++++++++++++----------- 4 files changed, 85 insertions(+), 53 deletions(-) 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/sync/sync.go b/polygon/sync/sync.go index 93b67b948e9..bd2cf823ba5 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 } @@ -760,10 +769,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,59 +819,70 @@ 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 + } - 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( @@ -861,9 +890,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 @@ -872,9 +902,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 @@ -882,7 +915,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 { @@ -894,7 +927,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 } tip = newTip @@ -906,7 +939,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 { From a1c438dc8028309dc8d30b38ddf430f3ae944a81 Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:22:23 +0000 Subject: [PATCH 10/42] polygon/heimdall: fix snapshot store last entity to check in snapshots too (#13845) highlighted by https://github.com/erigontech/erigon/issues/13746 relates to https://github.com/erigontech/erigon/pull/13844#discussion_r1958403527 snapshot store should return last entity from snapshots if there are none in the db --- polygon/heimdall/snapshot_store.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/polygon/heimdall/snapshot_store.go b/polygon/heimdall/snapshot_store.go index 5c752857fef..6fdb3d4f365 100644 --- a/polygon/heimdall/snapshot_store.go +++ b/polygon/heimdall/snapshot_store.go @@ -189,6 +189,10 @@ func (s *SpanSnapshotStore) LastEntityId(ctx context.Context) (uint64, bool, err return lastId, ok, err } +func (s *SpanSnapshotStore) LastEntity(ctx context.Context) (*Span, bool, error) { + return snapshotStoreLastEntity(ctx, s) +} + func (s *SpanSnapshotStore) ValidateSnapshots(logger log.Logger, failFast bool) error { return validateSnapshots(logger, failFast, s.snapshots, s.SnapType(), generics.New[Span]) } @@ -312,6 +316,10 @@ func (s *MilestoneSnapshotStore) Entity(ctx context.Context, id uint64) (*Milest return nil, false, fmt.Errorf("%w: %w", ErrMilestoneNotFound, err) } +func (s *MilestoneSnapshotStore) LastEntity(ctx context.Context) (*Milestone, bool, error) { + return snapshotStoreLastEntity(ctx, s) +} + func (s *MilestoneSnapshotStore) ValidateSnapshots(logger log.Logger, failFast bool) error { return validateSnapshots(logger, failFast, s.snapshots, s.SnapType(), generics.New[Milestone]) } @@ -345,7 +353,7 @@ func (s *CheckpointSnapshotStore) WithTx(tx kv.Tx) EntityStore[*Checkpoint] { return &CheckpointSnapshotStore{txEntityStore[*Checkpoint]{s.EntityStore.(*mdbxEntityStore[*Checkpoint]), tx}, s.snapshots} } -func (s *CheckpointSnapshotStore) LastCheckpointId(ctx context.Context, tx kv.Tx) (uint64, bool, error) { +func (s *CheckpointSnapshotStore) LastEntityId(ctx context.Context) (uint64, bool, error) { lastId, ok, err := s.EntityStore.LastEntityId(ctx) snapshotLastCheckpointId := s.LastFrozenEntityId() @@ -425,6 +433,10 @@ func (s *CheckpointSnapshotStore) LastFrozenEntityId() uint64 { return index.BaseDataID() + index.KeyCount() - 1 } +func (s *CheckpointSnapshotStore) LastEntity(ctx context.Context) (*Checkpoint, bool, error) { + return snapshotStoreLastEntity(ctx, s) +} + func (s *CheckpointSnapshotStore) ValidateSnapshots(logger log.Logger, failFast bool) error { return validateSnapshots(logger, failFast, s.snapshots, s.SnapType(), generics.New[Checkpoint]) } @@ -482,3 +494,12 @@ func validateSnapshots[T Entity](logger log.Logger, failFast bool, snaps *RoSnap return accumulatedErr } + +func snapshotStoreLastEntity[T Entity](ctx context.Context, store EntityStore[T]) (T, bool, error) { + entityId, ok, err := store.LastEntityId(ctx) + if err != nil || !ok { + return generics.Zero[T](), false, err + } + + return store.Entity(ctx, entityId) +} From b2ee8b60cbc8968d60c6ef01371c117a44fc32ed Mon Sep 17 00:00:00 2001 From: Ilya Mikheev <54912776+JkLondon@users.noreply.github.com> Date: Tue, 18 Feb 2025 07:30:49 +0400 Subject: [PATCH 11/42] Set default MDBX geometry configuration on existing dirs (#13800) closes #13581 --------- Co-authored-by: Ilya Miheev Co-authored-by: JkLondon --- erigon-lib/kv/mdbx/kv_mdbx.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erigon-lib/kv/mdbx/kv_mdbx.go b/erigon-lib/kv/mdbx/kv_mdbx.go index 7b281b40cf0..c0009292ef9 100644 --- a/erigon-lib/kv/mdbx/kv_mdbx.go +++ b/erigon-lib/kv/mdbx/kv_mdbx.go @@ -227,13 +227,22 @@ func (opts MdbxOpts) Open(ctx context.Context) (kv.RwDB, error) { return nil, err } - if !opts.HasFlag(mdbx.Accede) { + exists, err := dir.FileExist(filepath.Join(opts.path, "mdbx.dat")) + if err != nil { + return nil, err + } + + if !opts.HasFlag(mdbx.Accede) && !exists { if err = env.SetGeometry(-1, -1, int(opts.mapSize), int(opts.growthStep), opts.shrinkThreshold, int(opts.pageSize)); err != nil { return nil, err } if err = os.MkdirAll(opts.path, 0744); err != nil { return nil, fmt.Errorf("could not create dir: %s, %w", opts.path, err) } + } else if exists { + if err = env.SetGeometry(-1, -1, int(opts.mapSize), int(opts.growthStep), opts.shrinkThreshold, -1); err != nil { + return nil, err + } } // erigon using big transactions From e941501cad3052ef798b92c3568c22d7393ab616 Mon Sep 17 00:00:00 2001 From: Ostroukhov Nikita Date: Tue, 18 Feb 2025 08:32:06 +0000 Subject: [PATCH 12/42] Fixed panic on new mined block event when block download is disabled (#13849) --- eth/backend.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index 2e8313aee37..c5de8c5c287 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -889,14 +889,16 @@ 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()) //p2p //backend.sentriesClient.BroadcastNewBlock(context.Background(), b, b.Difficulty()) From 0afe7522ea4904f80fc6b530814e5535fea2a6d5 Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Tue, 18 Feb 2025 10:33:46 +0100 Subject: [PATCH 13/42] caplin: Omit marshalling if `nil` (#13832) Fixes issue using Caplin with `go-eth2-client` due to config JSON fields being set to `null`. --- cl/clparams/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cl/clparams/config.go b/cl/clparams/config.go index 715e3c152ef..610922497a7 100644 --- a/cl/clparams/config.go +++ b/cl/clparams/config.go @@ -210,8 +210,8 @@ type NetworkConfig struct { SyncCommsSubnetKey string `yaml:"-" json:"-"` // SyncCommsSubnetKey is the ENR key of the sync committee subnet bitfield in the enr. MinimumPeersInSubnetSearch uint64 `yaml:"-" json:"-"` // PeersInSubnetSearch is the required amount of peers that we need to be able to lookup in a subnet search. - BootNodes []string - StaticPeers []string + BootNodes []string `yaml:"-" json:"-"` + StaticPeers []string `yaml:"-" json:"-"` } var NetworkConfigs map[NetworkType]NetworkConfig = map[NetworkType]NetworkConfig{ From 84c4b3d4e372c52a9c7d0db3235327a839fafcfd Mon Sep 17 00:00:00 2001 From: lystopad Date: Tue, 18 Feb 2025 10:57:37 +0100 Subject: [PATCH 14/42] Add option to skip tests during release build. (#13855) --- .github/workflows/release.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a70ac2171f..90cb87e9d18 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ name: Release -run-name: Build release ${{ inputs.release_version}} from branch ${{ inputs.checkout_ref }} by @${{ github.actor }} +run-name: Build release ${{ inputs.release_version}} from branch ${{ inputs.checkout_ref }}, Skip tests=${{ inputs.skip_tests }} env: APPLICATION: "erigon" @@ -37,6 +37,11 @@ on: type: boolean default: false description: 'publish_latest_tag: when set then docker image with tag :latest will be also published' + skip_tests: + required: false + type: boolean + default: false + description: 'Skip tests during release build (not recommended)' jobs: @@ -199,6 +204,7 @@ jobs: test-release: name: test on ${{ matrix.id }} + if: ${{ ! inputs.skip_tests }} runs-on: [ self-hosted, Release, "${{ matrix.runner-arch }}" ] timeout-minutes: 7200 # 5 days needs: [ build-release ] @@ -425,8 +431,8 @@ jobs: In-case-of-failure: name: "In case of failure: remove remote git tag pointing to the new version." - needs: [ publish-release, build-release, test-release ] - if: always() && !contains(needs.build-release.result, 'success') + needs: [ publish-release, build-release, test-release, build-debian-pkg, publish-docker-image ] + if: always() && !contains(needs.build-release.result, 'success') && contains(needs.test-release.result, 'failure') && !contains(needs.publish-release.result, 'success') && !contains(needs.build-debian-pkg.result, 'success') && !contains(needs.publish-docker-image.result, 'success') runs-on: ubuntu-22.04 steps: From d015bc18a6490ea6517dfddc995b47c652d84a82 Mon Sep 17 00:00:00 2001 From: lystopad Date: Tue, 18 Feb 2025 13:43:34 +0100 Subject: [PATCH 15/42] Fix dependent jobs in release workflow (skip_tests case) (#13857) (#13859) --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 90cb87e9d18..ad04e922787 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -288,6 +288,7 @@ jobs: build-debian-pkg: name: Debian packages needs: [ build-release, test-release ] + if: always() && contains(needs.build-release.result, 'success') && !contains(needs.test-release.result, 'failure') uses: erigontech/erigon/.github/workflows/reusable-release-build-debian-pkg.yml@main with: application: ${{ needs.build-release.outputs.application }} @@ -296,6 +297,7 @@ jobs: publish-docker-image: needs: [ build-release, test-release ] + if: always() && contains(needs.build-release.result, 'success') && !contains(needs.test-release.result, 'failure') runs-on: ubuntu-latest timeout-minutes: 30 name: Docker image @@ -374,6 +376,7 @@ jobs: publish-release: needs: [ build-debian-pkg, publish-docker-image, build-release ] + if: always() && contains(needs.build-release.result, 'success') && contains(needs.build-debian-pkg.result, 'success') && contains(needs.publish-docker-image.result, 'success') runs-on: ubuntu-latest timeout-minutes: 15 name: Publish release notes From 500f1e60381703fb53f703fd32358cddf60ac81b Mon Sep 17 00:00:00 2001 From: Ilya Mikheev <54912776+JkLondon@users.noreply.github.com> Date: Tue, 18 Feb 2025 18:42:48 +0400 Subject: [PATCH 16/42] first receipt follow up (#13848) try to turn on disabled getlogs tests Co-authored-by: JkLondon --- .github/workflows/scripts/run_rpc_tests.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/scripts/run_rpc_tests.sh b/.github/workflows/scripts/run_rpc_tests.sh index 027eea7d74a..a8558abdd46 100755 --- a/.github/workflows/scripts/run_rpc_tests.sh +++ b/.github/workflows/scripts/run_rpc_tests.sh @@ -15,11 +15,6 @@ disabled_tests=( engine_exchangeTransitionConfigurationV1/test_01.json engine_getClientVersionV1/test_1.json # these tests require Fix on erigon DM on repeipts domain - eth_getLogs/test_16 - eth_getLogs/test_17 - eth_getLogs/test_18 - eth_getLogs/test_19 - eth_getLogs/test_20 # these tests requires Erigon active admin_nodeInfo/test_01.json admin_peers/test_01.json From 2ee3e5ccbc9363d40fd68431e2ba1f0417f0de75 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Tue, 18 Feb 2025 16:03:40 +0100 Subject: [PATCH 17/42] Caplin: now respecting max peer count (#13858) I also removed some code from `discovery` which is mostly useless --- cl/sentinel/discovery.go | 10 +- cl/sentinel/sentinel.go | 169 +++++++++++++------------- cl/sentinel/sentinel_gossip_test.go | 2 + cl/sentinel/sentinel_requests_test.go | 3 + 4 files changed, 96 insertions(+), 88 deletions(-) diff --git a/cl/sentinel/discovery.go b/cl/sentinel/discovery.go index 384c752dc9a..53a840eb08b 100644 --- a/cl/sentinel/discovery.go +++ b/cl/sentinel/discovery.go @@ -114,8 +114,8 @@ func (s *Sentinel) listenForPeers() { } node := iterator.Node() - needsPeersForSubnets := s.isPeerUsefulForAnySubnet(node) - if s.HasTooManyPeers() && !needsPeersForSubnets { + // needsPeersForSubnets := s.isPeerUsefulForAnySubnet(node) + if s.HasTooManyPeers() { log.Trace("[Sentinel] Not looking for peers, at peer limit") time.Sleep(100 * time.Millisecond) continue @@ -179,6 +179,12 @@ func (s *Sentinel) setupENR( func (s *Sentinel) onConnection(net network.Network, conn network.Conn) { go func() { peerId := conn.RemotePeer() + if s.HasTooManyPeers() { + log.Trace("[Sentinel] Not looking for peers, at peer limit") + s.host.Peerstore().RemovePeer(peerId) + s.host.Network().ClosePeer(peerId) + s.peers.RemovePeer(peerId) + } valid, err := s.handshaker.ValidatePeer(peerId) if err != nil { log.Trace("[sentinel] failed to validate peer:", "err", err) diff --git a/cl/sentinel/sentinel.go b/cl/sentinel/sentinel.go index 729becd1dca..447f216d293 100644 --- a/cl/sentinel/sentinel.go +++ b/cl/sentinel/sentinel.go @@ -23,7 +23,6 @@ import ( "net" "net/http" "os/signal" - "strconv" "strings" "sync" "syscall" @@ -31,7 +30,6 @@ import ( "github.com/c2h5oh/datasize" "github.com/go-chi/chi/v5" - "github.com/prysmaticlabs/go-bitfield" "github.com/libp2p/go-libp2p" pubsub "github.com/libp2p/go-libp2p-pubsub" @@ -45,7 +43,6 @@ import ( "github.com/erigontech/erigon-lib/kv" "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon/cl/cltypes" - "github.com/erigontech/erigon/cl/gossip" "github.com/erigontech/erigon/cl/monitor" "github.com/erigontech/erigon/cl/persistence/blob_storage" "github.com/erigontech/erigon/cl/phase1/forkchoice" @@ -386,89 +383,89 @@ func (s *Sentinel) HasTooManyPeers() bool { return active >= int(s.cfg.MaxPeerCount) } -func (s *Sentinel) isPeerUsefulForAnySubnet(node *enode.Node) bool { - ret := false - - nodeAttnets := bitfield.NewBitvector64() - nodeSyncnets := bitfield.NewBitvector4() - if err := node.Load(enr.WithEntry(s.cfg.NetworkConfig.AttSubnetKey, &nodeAttnets)); err != nil { - log.Trace("Could not load att subnet", "err", err) - return false - } - if err := node.Load(enr.WithEntry(s.cfg.NetworkConfig.SyncCommsSubnetKey, &nodeSyncnets)); err != nil { - log.Trace("Could not load sync subnet", "err", err) - return false - } - - s.subManager.subscriptions.Range(func(key, value any) bool { - sub := value.(*GossipSubscription) - sub.lock.Lock() - defer sub.lock.Unlock() - if sub.sub == nil { - return true - } - - if !sub.subscribed.Load() { - return true - } - - if len(sub.topic.ListPeers()) > peerSubnetTarget { - return true - } - if gossip.IsTopicBeaconAttestation(sub.sub.Topic()) { - ret = s.isPeerUsefulForAttNet(sub, nodeAttnets) - return !ret - } - - if gossip.IsTopicSyncCommittee(sub.sub.Topic()) { - ret = s.isPeerUsefulForSyncNet(sub, nodeSyncnets) - return !ret - } - - return true - }) - return ret -} - -func (s *Sentinel) isPeerUsefulForAttNet(sub *GossipSubscription, nodeAttnets bitfield.Bitvector64) bool { - splitTopic := strings.Split(sub.sub.Topic(), "/") - if len(splitTopic) < 4 { - return false - } - subnetIdStr, found := strings.CutPrefix(splitTopic[3], "beacon_attestation_") - if !found { - return false - } - subnetId, err := strconv.Atoi(subnetIdStr) - if err != nil { - log.Warn("Could not parse subnet id", "subnet", subnetIdStr, "err", err) - return false - } - // check if subnetIdth bit is set in nodeAttnets - return nodeAttnets.BitAt(uint64(subnetId)) - -} - -func (s *Sentinel) isPeerUsefulForSyncNet(sub *GossipSubscription, nodeSyncnets bitfield.Bitvector4) bool { - splitTopic := strings.Split(sub.sub.Topic(), "/") - if len(splitTopic) < 4 { - return false - } - syncnetIdStr, found := strings.CutPrefix(splitTopic[3], "sync_committee_") - if !found { - return false - } - syncnetId, err := strconv.Atoi(syncnetIdStr) - if err != nil { - log.Warn("Could not parse syncnet id", "syncnet", syncnetIdStr, "err", err) - return false - } - // check if syncnetIdth bit is set in nodeSyncnets - if nodeSyncnets.BitAt(uint64(syncnetId)) { - return true - } - return false -} +// func (s *Sentinel) isPeerUsefulForAnySubnet(node *enode.Node) bool { +// ret := false + +// nodeAttnets := bitfield.NewBitvector64() +// nodeSyncnets := bitfield.NewBitvector4() +// if err := node.Load(enr.WithEntry(s.cfg.NetworkConfig.AttSubnetKey, &nodeAttnets)); err != nil { +// log.Trace("Could not load att subnet", "err", err) +// return false +// } +// if err := node.Load(enr.WithEntry(s.cfg.NetworkConfig.SyncCommsSubnetKey, &nodeSyncnets)); err != nil { +// log.Trace("Could not load sync subnet", "err", err) +// return false +// } + +// s.subManager.subscriptions.Range(func(key, value any) bool { +// sub := value.(*GossipSubscription) +// sub.lock.Lock() +// defer sub.lock.Unlock() +// if sub.sub == nil { +// return true +// } + +// if !sub.subscribed.Load() { +// return true +// } + +// if len(sub.topic.ListPeers()) > peerSubnetTarget { +// return true +// } +// if gossip.IsTopicBeaconAttestation(sub.sub.Topic()) { +// ret = s.isPeerUsefulForAttNet(sub, nodeAttnets) +// return !ret +// } + +// if gossip.IsTopicSyncCommittee(sub.sub.Topic()) { +// ret = s.isPeerUsefulForSyncNet(sub, nodeSyncnets) +// return !ret +// } + +// return true +// }) +// return ret +// } + +// func (s *Sentinel) isPeerUsefulForAttNet(sub *GossipSubscription, nodeAttnets bitfield.Bitvector64) bool { +// splitTopic := strings.Split(sub.sub.Topic(), "/") +// if len(splitTopic) < 4 { +// return false +// } +// subnetIdStr, found := strings.CutPrefix(splitTopic[3], "beacon_attestation_") +// if !found { +// return false +// } +// subnetId, err := strconv.Atoi(subnetIdStr) +// if err != nil { +// log.Warn("Could not parse subnet id", "subnet", subnetIdStr, "err", err) +// return false +// } +// // check if subnetIdth bit is set in nodeAttnets +// return nodeAttnets.BitAt(uint64(subnetId)) + +// } + +// func (s *Sentinel) isPeerUsefulForSyncNet(sub *GossipSubscription, nodeSyncnets bitfield.Bitvector4) bool { +// splitTopic := strings.Split(sub.sub.Topic(), "/") +// if len(splitTopic) < 4 { +// return false +// } +// syncnetIdStr, found := strings.CutPrefix(splitTopic[3], "sync_committee_") +// if !found { +// return false +// } +// syncnetId, err := strconv.Atoi(syncnetIdStr) +// if err != nil { +// log.Warn("Could not parse syncnet id", "syncnet", syncnetIdStr, "err", err) +// return false +// } +// // check if syncnetIdth bit is set in nodeSyncnets +// if nodeSyncnets.BitAt(uint64(syncnetId)) { +// return true +// } +// return false +// } func (s *Sentinel) GetPeersCount() (active int, connected int, disconnected int) { peers := s.host.Network().Peers() diff --git a/cl/sentinel/sentinel_gossip_test.go b/cl/sentinel/sentinel_gossip_test.go index 9773e956e34..c9890d2b837 100644 --- a/cl/sentinel/sentinel_gossip_test.go +++ b/cl/sentinel/sentinel_gossip_test.go @@ -63,6 +63,7 @@ func TestSentinelGossipOnHardFork(t *testing.T) { IpAddr: listenAddrHost, Port: 7070, EnableBlocks: true, + MaxPeerCount: 9999999, }, ethClock, reader, nil, db, log.New(), &mock_services.ForkChoiceStorageMock{}) require.NoError(t, err) defer sentinel1.Stop() @@ -77,6 +78,7 @@ func TestSentinelGossipOnHardFork(t *testing.T) { Port: 7077, EnableBlocks: true, TCPPort: 9123, + MaxPeerCount: 9999999, }, ethClock, reader, nil, db, log.New(), &mock_services.ForkChoiceStorageMock{}) require.NoError(t, err) defer sentinel2.Stop() diff --git a/cl/sentinel/sentinel_requests_test.go b/cl/sentinel/sentinel_requests_test.go index c2eac7403c7..cedf3aec840 100644 --- a/cl/sentinel/sentinel_requests_test.go +++ b/cl/sentinel/sentinel_requests_test.go @@ -77,6 +77,7 @@ func TestSentinelBlocksByRange(t *testing.T) { IpAddr: listenAddrHost, Port: 7070, EnableBlocks: true, + MaxPeerCount: 8883, }, ethClock, reader, nil, db, log.New(), &mock_services.ForkChoiceStorageMock{}) require.NoError(t, err) defer sentinel.Stop() @@ -181,6 +182,7 @@ func TestSentinelBlocksByRoots(t *testing.T) { IpAddr: listenAddrHost, Port: 7070, EnableBlocks: true, + MaxPeerCount: 8883, }, ethClock, reader, nil, db, log.New(), &mock_services.ForkChoiceStorageMock{}) require.NoError(t, err) defer sentinel.Stop() @@ -290,6 +292,7 @@ func TestSentinelStatusRequest(t *testing.T) { IpAddr: listenAddrHost, Port: 7070, EnableBlocks: true, + MaxPeerCount: 8883, }, ethClock, reader, nil, db, log.New(), &mock_services.ForkChoiceStorageMock{}) require.NoError(t, err) defer sentinel.Stop() From 4d73ef5fc2a75ecd0ff1299376d015379aab7d57 Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:55:07 +0000 Subject: [PATCH 18/42] shards: add block time to state changes (#13860) add `BlockTime` info to `StateChange` message this is needed in the Shutter pool to be able to calculate the slot number associated with the given block notification used in https://github.com/erigontech/erigon/pull/13864 relates to https://github.com/erigontech/erigon/issues/13386 --- erigon-lib/go.mod | 2 +- erigon-lib/go.sum | 4 +- .../downloaderproto/downloader.pb.go | 14 +- .../executionproto/execution.pb.go | 14 +- erigon-lib/gointerfaces/remoteproto/bor.pb.go | 14 +- .../gointerfaces/remoteproto/ethbackend.pb.go | 14 +- erigon-lib/gointerfaces/remoteproto/kv.pb.go | 370 +++++++++--------- .../gointerfaces/sentinelproto/sentinel.pb.go | 14 +- .../gointerfaces/sentryproto/sentry.pb.go | 14 +- .../gointerfaces/txpoolproto/mining.pb.go | 14 +- .../gointerfaces/txpoolproto/txpool.pb.go | 14 +- .../gointerfaces/typesproto/types.pb.go | 14 +- eth/stagedsync/exec3.go | 2 +- eth/stagedsync/stage_execute.go | 9 +- turbo/shards/state_change_accumulator.go | 8 +- turbo/stages/stageloop.go | 2 +- 16 files changed, 271 insertions(+), 252 deletions(-) diff --git a/erigon-lib/go.mod b/erigon-lib/go.mod index dfac6911ad6..4f6b7670198 100644 --- a/erigon-lib/go.mod +++ b/erigon-lib/go.mod @@ -10,7 +10,7 @@ replace ( require ( github.com/erigontech/erigon-snapshot v1.3.1-0.20250121111444-6cc4c0c1fb89 - github.com/erigontech/interfaces v0.0.0-20241120074553-214b5fd396ed + github.com/erigontech/interfaces v0.0.0-20250218124515-7c4293b6afb3 github.com/erigontech/mdbx-go v0.38.6-0.20250205222432-e4dd01978d7f github.com/erigontech/secp256k1 v1.1.0 github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 diff --git a/erigon-lib/go.sum b/erigon-lib/go.sum index 7e073ded53d..bc8947c9cd7 100644 --- a/erigon-lib/go.sum +++ b/erigon-lib/go.sum @@ -154,8 +154,8 @@ github.com/erigontech/erigon-snapshot v1.3.1-0.20250121111444-6cc4c0c1fb89 h1:7N github.com/erigontech/erigon-snapshot v1.3.1-0.20250121111444-6cc4c0c1fb89/go.mod h1:ooHlCl+eEYzebiPu+FP6Q6SpPUeMADn8Jxabv3IKb9M= github.com/erigontech/go-kzg-4844 v0.0.0-20250130131058-ce13be60bc86 h1:UKcIbFZUGIKzK4aQbkv/dYiOVxZSUuD3zKadhmfwdwU= github.com/erigontech/go-kzg-4844 v0.0.0-20250130131058-ce13be60bc86/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= -github.com/erigontech/interfaces v0.0.0-20241120074553-214b5fd396ed h1:un44S8Tuol4LBIC6R94t93GShM53BYjz7GsNPziDLQ8= -github.com/erigontech/interfaces v0.0.0-20241120074553-214b5fd396ed/go.mod h1:N7OUkhkcagp9+7yb4ycHsG2VWCOmuJ1ONBecJshxtLE= +github.com/erigontech/interfaces v0.0.0-20250218124515-7c4293b6afb3 h1:Qwd3asRe5aeKzV/oHgADLu92db2417AM5ApjpT8MD1o= +github.com/erigontech/interfaces v0.0.0-20250218124515-7c4293b6afb3/go.mod h1:N7OUkhkcagp9+7yb4ycHsG2VWCOmuJ1ONBecJshxtLE= github.com/erigontech/mdbx-go v0.38.6-0.20250205222432-e4dd01978d7f h1:2uZv1SGd7bIfdjUVx6GWdg9xNBWWc84iXSiOeeKpaOQ= github.com/erigontech/mdbx-go v0.38.6-0.20250205222432-e4dd01978d7f/go.mod h1:lkqHAZqXtFaIPlvTaGAx3VUDuGYZcuhve1l4JVVN1Z0= github.com/erigontech/secp256k1 v1.1.0 h1:mO3YJMUSoASE15Ya//SoHiisptUhdXExuMUN1M0X9qY= diff --git a/erigon-lib/gointerfaces/downloaderproto/downloader.pb.go b/erigon-lib/gointerfaces/downloaderproto/downloader.pb.go index 15aa720b3c1..29e6c17f1d5 100644 --- a/erigon-lib/gointerfaces/downloaderproto/downloader.pb.go +++ b/erigon-lib/gointerfaces/downloaderproto/downloader.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: downloader/downloader.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -463,7 +464,7 @@ func (x *TorrentCompletedReply) GetHash() *typesproto.H160 { var File_downloader_downloader_proto protoreflect.FileDescriptor -var file_downloader_downloader_proto_rawDesc = []byte{ +var file_downloader_downloader_proto_rawDesc = string([]byte{ 0x0a, 0x1b, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, @@ -535,16 +536,16 @@ var file_downloader_downloader_proto_rawDesc = []byte{ 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x3b, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_downloader_downloader_proto_rawDescOnce sync.Once - file_downloader_downloader_proto_rawDescData = file_downloader_downloader_proto_rawDesc + file_downloader_downloader_proto_rawDescData []byte ) func file_downloader_downloader_proto_rawDescGZIP() []byte { file_downloader_downloader_proto_rawDescOnce.Do(func() { - file_downloader_downloader_proto_rawDescData = protoimpl.X.CompressGZIP(file_downloader_downloader_proto_rawDescData) + file_downloader_downloader_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_downloader_downloader_proto_rawDesc), len(file_downloader_downloader_proto_rawDesc))) }) return file_downloader_downloader_proto_rawDescData } @@ -598,7 +599,7 @@ func file_downloader_downloader_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_downloader_downloader_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_downloader_downloader_proto_rawDesc), len(file_downloader_downloader_proto_rawDesc)), NumEnums: 0, NumMessages: 10, NumExtensions: 0, @@ -609,7 +610,6 @@ func file_downloader_downloader_proto_init() { MessageInfos: file_downloader_downloader_proto_msgTypes, }.Build() File_downloader_downloader_proto = out.File - file_downloader_downloader_proto_rawDesc = nil file_downloader_downloader_proto_goTypes = nil file_downloader_downloader_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/executionproto/execution.pb.go b/erigon-lib/gointerfaces/executionproto/execution.pb.go index 0001a7edf8e..bf640991c75 100644 --- a/erigon-lib/gointerfaces/executionproto/execution.pb.go +++ b/erigon-lib/gointerfaces/executionproto/execution.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: execution/execution.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -1624,7 +1625,7 @@ func (x *HasBlockResponse) GetHasBlock() bool { var File_execution_execution_proto protoreflect.FileDescriptor -var file_execution_execution_proto_rawDesc = []byte{ +var file_execution_execution_proto_rawDesc = string([]byte{ 0x0a, 0x19, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, @@ -1976,16 +1977,16 @@ var file_execution_execution_proto_rawDesc = []byte{ 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x1c, 0x5a, 0x1a, 0x2e, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_execution_execution_proto_rawDescOnce sync.Once - file_execution_execution_proto_rawDescData = file_execution_execution_proto_rawDesc + file_execution_execution_proto_rawDescData []byte ) func file_execution_execution_proto_rawDescGZIP() []byte { file_execution_execution_proto_rawDescOnce.Do(func() { - file_execution_execution_proto_rawDescData = protoimpl.X.CompressGZIP(file_execution_execution_proto_rawDescData) + file_execution_execution_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_execution_execution_proto_rawDesc), len(file_execution_execution_proto_rawDesc))) }) return file_execution_execution_proto_rawDescData } @@ -2134,7 +2135,7 @@ func file_execution_execution_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_execution_execution_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_execution_execution_proto_rawDesc), len(file_execution_execution_proto_rawDesc)), NumEnums: 1, NumMessages: 26, NumExtensions: 0, @@ -2146,7 +2147,6 @@ func file_execution_execution_proto_init() { MessageInfos: file_execution_execution_proto_msgTypes, }.Build() File_execution_execution_proto = out.File - file_execution_execution_proto_rawDesc = nil file_execution_execution_proto_goTypes = nil file_execution_execution_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/remoteproto/bor.pb.go b/erigon-lib/gointerfaces/remoteproto/bor.pb.go index 90b7abdab33..36de2105028 100644 --- a/erigon-lib/gointerfaces/remoteproto/bor.pb.go +++ b/erigon-lib/gointerfaces/remoteproto/bor.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: remote/bor.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -388,7 +389,7 @@ func (x *Validator) GetProposerPriority() int64 { var File_remote_bor_proto protoreflect.FileDescriptor -var file_remote_bor_proto_rawDesc = []byte{ +var file_remote_bor_proto_rawDesc = string([]byte{ 0x0a, 0x10, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2f, 0x62, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, @@ -461,16 +462,16 @@ var file_remote_bor_proto_rawDesc = []byte{ 0x75, 0x63, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x3b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_remote_bor_proto_rawDescOnce sync.Once - file_remote_bor_proto_rawDescData = file_remote_bor_proto_rawDesc + file_remote_bor_proto_rawDescData []byte ) func file_remote_bor_proto_rawDescGZIP() []byte { file_remote_bor_proto_rawDescOnce.Do(func() { - file_remote_bor_proto_rawDescData = protoimpl.X.CompressGZIP(file_remote_bor_proto_rawDescData) + file_remote_bor_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_remote_bor_proto_rawDesc), len(file_remote_bor_proto_rawDesc))) }) return file_remote_bor_proto_rawDescData } @@ -521,7 +522,7 @@ func file_remote_bor_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_remote_bor_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_remote_bor_proto_rawDesc), len(file_remote_bor_proto_rawDesc)), NumEnums: 0, NumMessages: 7, NumExtensions: 0, @@ -532,7 +533,6 @@ func file_remote_bor_proto_init() { MessageInfos: file_remote_bor_proto_msgTypes, }.Build() File_remote_bor_proto = out.File - file_remote_bor_proto_rawDesc = nil file_remote_bor_proto_goTypes = nil file_remote_bor_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/remoteproto/ethbackend.pb.go b/erigon-lib/gointerfaces/remoteproto/ethbackend.pb.go index f532eed81bc..15cf024dee0 100644 --- a/erigon-lib/gointerfaces/remoteproto/ethbackend.pb.go +++ b/erigon-lib/gointerfaces/remoteproto/ethbackend.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: remote/ethbackend.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -1703,7 +1704,7 @@ func (x *SyncingReply_StageProgress) GetBlockNumber() uint64 { var File_remote_ethbackend_proto protoreflect.FileDescriptor -var file_remote_ethbackend_proto_rawDesc = []byte{ +var file_remote_ethbackend_proto_rawDesc = string([]byte{ 0x0a, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2f, 0x65, 0x74, 0x68, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, @@ -1954,16 +1955,16 @@ var file_remote_ethbackend_proto_rawDesc = []byte{ 0x70, 0x6c, 0x79, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x3b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_remote_ethbackend_proto_rawDescOnce sync.Once - file_remote_ethbackend_proto_rawDescData = file_remote_ethbackend_proto_rawDesc + file_remote_ethbackend_proto_rawDescData []byte ) func file_remote_ethbackend_proto_rawDescGZIP() []byte { file_remote_ethbackend_proto_rawDescOnce.Do(func() { - file_remote_ethbackend_proto_rawDescData = protoimpl.X.CompressGZIP(file_remote_ethbackend_proto_rawDescData) + file_remote_ethbackend_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_remote_ethbackend_proto_rawDesc), len(file_remote_ethbackend_proto_rawDesc))) }) return file_remote_ethbackend_proto_rawDescData } @@ -2093,7 +2094,7 @@ func file_remote_ethbackend_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_remote_ethbackend_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_remote_ethbackend_proto_rawDesc), len(file_remote_ethbackend_proto_rawDesc)), NumEnums: 1, NumMessages: 34, NumExtensions: 0, @@ -2105,7 +2106,6 @@ func file_remote_ethbackend_proto_init() { MessageInfos: file_remote_ethbackend_proto_msgTypes, }.Build() File_remote_ethbackend_proto = out.File - file_remote_ethbackend_proto_rawDesc = nil file_remote_ethbackend_proto_goTypes = nil file_remote_ethbackend_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/remoteproto/kv.pb.go b/erigon-lib/gointerfaces/remoteproto/kv.pb.go index 1d8bed85ac9..417ce152cdc 100644 --- a/erigon-lib/gointerfaces/remoteproto/kv.pb.go +++ b/erigon-lib/gointerfaces/remoteproto/kv.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: remote/kv.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -598,6 +599,7 @@ type StateChange struct { BlockHash *typesproto.H256 `protobuf:"bytes,3,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` Changes []*AccountChange `protobuf:"bytes,4,rep,name=changes,proto3" json:"changes,omitempty"` Txs [][]byte `protobuf:"bytes,5,rep,name=txs,proto3" json:"txs,omitempty"` // enable by withTransactions=true + BlockTime uint64 `protobuf:"varint,6,opt,name=block_time,json=blockTime,proto3" json:"block_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -667,6 +669,13 @@ func (x *StateChange) GetTxs() [][]byte { return nil } +func (x *StateChange) GetBlockTime() uint64 { + if x != nil { + return x.BlockTime + } + return 0 +} + type StateChangeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` WithStorage bool `protobuf:"varint,1,opt,name=with_storage,json=withStorage,proto3" json:"with_storage,omitempty"` @@ -1715,7 +1724,7 @@ func (x *IndexPagination) GetLimit() int64 { var File_remote_kv_proto protoreflect.FileDescriptor -var file_remote_kv_proto_rawDesc = []byte{ +var file_remote_kv_proto_rawDesc = string([]byte{ 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2f, 0x6b, 0x76, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, @@ -1774,7 +1783,7 @@ var file_remote_kv_proto_rawDesc = []byte{ 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6c, 0x6f, 0x62, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, - 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x73, 0x22, 0xef, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, @@ -1787,192 +1796,194 @@ var file_remote_kv_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x78, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, - 0x03, 0x74, 0x78, 0x73, 0x22, 0x64, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x69, - 0x74, 0x68, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, - 0x11, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x77, 0x69, 0x74, 0x68, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x58, - 0x0a, 0x0e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x46, 0x69, - 0x6c, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x52, 0x61, 0x6e, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x03, 0x74, 0x78, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x54, + 0x69, 0x6d, 0x65, 0x22, 0x64, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x69, 0x74, + 0x68, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0b, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x11, + 0x77, 0x69, 0x74, 0x68, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x77, 0x69, 0x74, 0x68, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x58, 0x0a, + 0x0e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x21, 0x0a, 0x0c, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x46, 0x69, 0x6c, + 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x5f, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xe8, 0x01, 0x0a, 0x08, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, + 0x1f, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, + 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x6f, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x21, 0x0a, + 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x12, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x7f, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x0c, 0x0a, + 0x01, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x74, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x6b, + 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x6b, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x6c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x01, 0x76, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x02, 0x6f, 0x6b, 0x22, 0x59, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, + 0x65, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x6f, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x21, - 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x41, 0x73, 0x63, 0x65, 0x6e, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x12, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, - 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x22, 0x7f, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x0c, - 0x0a, 0x01, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x6b, 0x12, 0x0e, 0x0a, 0x02, - 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x12, 0x0e, 0x0a, 0x02, - 0x6b, 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x6b, 0x32, 0x12, 0x16, 0x0a, 0x06, - 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x01, 0x76, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x59, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, - 0x65, 0x65, 0x6b, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x12, 0x0c, 0x0a, 0x01, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x6b, 0x12, - 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x22, - 0x30, 0x0a, 0x10, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, - 0x76, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, - 0x6b, 0x22, 0xeb, 0x01, 0x0a, 0x0d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, - 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x0c, - 0x0a, 0x01, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x6b, 0x12, 0x17, 0x0a, 0x07, - 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, 0x66, - 0x72, 0x6f, 0x6d, 0x54, 0x73, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x6f, 0x5f, 0x74, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x12, 0x52, 0x04, 0x74, 0x6f, 0x54, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, - 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, - 0x59, 0x0a, 0x0f, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, - 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xdf, 0x01, 0x0a, 0x0f, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x13, - 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, - 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x72, 0x6f, - 0x6d, 0x5f, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, 0x66, 0x72, 0x6f, 0x6d, - 0x54, 0x73, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x6f, 0x5f, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x12, 0x52, 0x04, 0x74, 0x6f, 0x54, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x5f, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x88, 0x02, 0x0a, - 0x0c, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x4f, 0x66, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, + 0x12, 0x0c, 0x0a, 0x01, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x6b, 0x12, 0x0e, + 0x0a, 0x02, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x22, 0x30, + 0x0a, 0x10, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x76, + 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, + 0x22, 0xeb, 0x01, 0x0a, 0x0d, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x0c, 0x0a, + 0x01, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x6b, 0x12, 0x17, 0x0a, 0x07, 0x66, + 0x72, 0x6f, 0x6d, 0x5f, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, 0x66, 0x72, + 0x6f, 0x6d, 0x54, 0x73, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x6f, 0x5f, 0x74, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x12, 0x52, 0x04, 0x74, 0x6f, 0x54, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x5f, 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x59, + 0x0a, 0x0f, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xdf, 0x01, 0x0a, 0x0f, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x72, 0x6f, 0x6d, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x66, 0x72, 0x6f, 0x6d, - 0x4b, 0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x61, 0x74, 0x65, - 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x63, 0x65, - 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x41, - 0x73, 0x63, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, - 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, - 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5b, 0x0a, 0x05, 0x50, 0x61, 0x69, 0x72, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, - 0x6b, 0x65, 0x79, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, - 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x42, 0x0a, 0x0f, 0x50, 0x61, 0x69, 0x72, 0x73, 0x50, 0x61, 0x67, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x4f, 0x0a, 0x0f, 0x49, 0x6e, 0x64, 0x65, - 0x78, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6e, - 0x65, 0x78, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x12, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x74, - 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2a, 0xfb, 0x01, 0x0a, 0x02, 0x4f, 0x70, - 0x12, 0x09, 0x0a, 0x05, 0x46, 0x49, 0x52, 0x53, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, - 0x49, 0x52, 0x53, 0x54, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, - 0x45, 0x4b, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x45, 0x45, 0x4b, 0x5f, 0x42, 0x4f, 0x54, - 0x48, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x10, 0x04, - 0x12, 0x08, 0x0a, 0x04, 0x4c, 0x41, 0x53, 0x54, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x41, - 0x53, 0x54, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x07, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x45, 0x58, 0x54, - 0x10, 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x09, - 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x55, 0x50, 0x10, - 0x0b, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x52, 0x45, 0x56, 0x10, 0x0c, 0x12, 0x0c, 0x0a, 0x08, 0x50, - 0x52, 0x45, 0x56, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x0d, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x45, - 0x56, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x0e, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x45, - 0x45, 0x4b, 0x5f, 0x45, 0x58, 0x41, 0x43, 0x54, 0x10, 0x0f, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, - 0x45, 0x4b, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x5f, 0x45, 0x58, 0x41, 0x43, 0x54, 0x10, 0x10, 0x12, - 0x08, 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x1e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, - 0x53, 0x45, 0x10, 0x1f, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x44, 0x55, 0x50, - 0x5f, 0x53, 0x4f, 0x52, 0x54, 0x10, 0x20, 0x2a, 0x48, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x0a, - 0x0a, 0x06, 0x55, 0x50, 0x53, 0x45, 0x52, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, - 0x44, 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x50, 0x53, 0x45, 0x52, 0x54, 0x5f, 0x43, - 0x4f, 0x44, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, - 0x04, 0x2a, 0x24, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, - 0x0a, 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, - 0x4e, 0x57, 0x49, 0x4e, 0x44, 0x10, 0x01, 0x32, 0xb9, 0x04, 0x0a, 0x02, 0x4b, 0x56, 0x12, 0x36, - 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x02, 0x54, 0x78, 0x12, 0x0e, 0x2e, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x1a, 0x0c, 0x2e, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, - 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x1a, - 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x6d, - 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x30, 0x01, 0x12, 0x3d, 0x0a, 0x09, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x6e, 0x61, - 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x10, - 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x1a, 0x0d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, - 0x39, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x12, 0x14, 0x2e, 0x72, - 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x4c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0b, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x52, 0x65, - 0x71, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3c, 0x0a, 0x0a, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, - 0x1a, 0x17, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, - 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x36, 0x0a, 0x0c, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, - 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, 0x72, - 0x73, 0x12, 0x30, 0x0a, 0x09, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x4f, 0x66, 0x12, 0x14, - 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x4f, - 0x66, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, - 0x69, 0x72, 0x73, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x3b, - 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} + 0x09, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x72, 0x6f, 0x6d, + 0x5f, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x12, 0x52, 0x06, 0x66, 0x72, 0x6f, 0x6d, 0x54, + 0x73, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x6f, 0x5f, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x12, + 0x52, 0x04, 0x74, 0x6f, 0x54, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, + 0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x41, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x88, 0x02, 0x0a, 0x0c, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x4f, 0x66, 0x52, 0x65, 0x71, 0x12, 0x13, 0x0a, 0x05, + 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x78, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x66, 0x72, 0x6f, 0x6d, 0x4b, + 0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x74, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x73, 0x63, 0x65, 0x6e, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x41, 0x73, + 0x63, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, + 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5b, 0x0a, 0x05, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x6b, + 0x65, 0x79, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0c, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, + 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x22, 0x42, 0x0a, 0x0f, 0x50, 0x61, 0x69, 0x72, 0x73, 0x50, 0x61, 0x67, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x78, 0x74, 0x4b, 0x65, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x12, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x4f, 0x0a, 0x0f, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, + 0x78, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x12, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x12, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x2a, 0xfb, 0x01, 0x0a, 0x02, 0x4f, 0x70, 0x12, + 0x09, 0x0a, 0x05, 0x46, 0x49, 0x52, 0x53, 0x54, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x49, + 0x52, 0x53, 0x54, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x45, + 0x4b, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x45, 0x45, 0x4b, 0x5f, 0x42, 0x4f, 0x54, 0x48, + 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x55, 0x52, 0x52, 0x45, 0x4e, 0x54, 0x10, 0x04, 0x12, + 0x08, 0x0a, 0x04, 0x4c, 0x41, 0x53, 0x54, 0x10, 0x06, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x41, 0x53, + 0x54, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x07, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x45, 0x58, 0x54, 0x10, + 0x08, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x09, 0x12, + 0x0f, 0x0a, 0x0b, 0x4e, 0x45, 0x58, 0x54, 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x0b, + 0x12, 0x08, 0x0a, 0x04, 0x50, 0x52, 0x45, 0x56, 0x10, 0x0c, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52, + 0x45, 0x56, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x0d, 0x12, 0x0f, 0x0a, 0x0b, 0x50, 0x52, 0x45, 0x56, + 0x5f, 0x4e, 0x4f, 0x5f, 0x44, 0x55, 0x50, 0x10, 0x0e, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x45, 0x45, + 0x4b, 0x5f, 0x45, 0x58, 0x41, 0x43, 0x54, 0x10, 0x0f, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x45, + 0x4b, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x5f, 0x45, 0x58, 0x41, 0x43, 0x54, 0x10, 0x10, 0x12, 0x08, + 0x0a, 0x04, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x1e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, + 0x45, 0x10, 0x1f, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x50, 0x45, 0x4e, 0x5f, 0x44, 0x55, 0x50, 0x5f, + 0x53, 0x4f, 0x52, 0x54, 0x10, 0x20, 0x2a, 0x48, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, + 0x06, 0x55, 0x50, 0x53, 0x45, 0x52, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x44, + 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x50, 0x53, 0x45, 0x52, 0x54, 0x5f, 0x43, 0x4f, + 0x44, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x04, + 0x2a, 0x24, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, + 0x07, 0x46, 0x4f, 0x52, 0x57, 0x41, 0x52, 0x44, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x4e, + 0x57, 0x49, 0x4e, 0x44, 0x10, 0x01, 0x32, 0xb9, 0x04, 0x0a, 0x02, 0x4b, 0x56, 0x12, 0x36, 0x0a, + 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x02, 0x54, 0x78, 0x12, 0x0e, 0x2e, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x1a, 0x0c, 0x2e, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, + 0x0c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x1a, 0x2e, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x6d, 0x6f, + 0x74, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x61, + 0x74, 0x63, 0x68, 0x30, 0x01, 0x12, 0x3d, 0x0a, 0x09, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x73, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x10, 0x2e, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, + 0x0d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x39, + 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x12, 0x14, 0x2e, 0x72, 0x65, + 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0b, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x65, 0x6b, 0x52, 0x65, 0x71, + 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, + 0x79, 0x53, 0x65, 0x65, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3c, 0x0a, 0x0a, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, + 0x17, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x61, + 0x6e, 0x67, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x36, 0x0a, 0x0c, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x72, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x1a, 0x0d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x73, + 0x12, 0x30, 0x0a, 0x09, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x4f, 0x66, 0x12, 0x14, 0x2e, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x73, 0x4f, 0x66, + 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x2e, 0x50, 0x61, 0x69, + 0x72, 0x73, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x3b, 0x72, + 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +}) var ( file_remote_kv_proto_rawDescOnce sync.Once - file_remote_kv_proto_rawDescData = file_remote_kv_proto_rawDesc + file_remote_kv_proto_rawDescData []byte ) func file_remote_kv_proto_rawDescGZIP() []byte { file_remote_kv_proto_rawDescOnce.Do(func() { - file_remote_kv_proto_rawDescData = protoimpl.X.CompressGZIP(file_remote_kv_proto_rawDescData) + file_remote_kv_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_remote_kv_proto_rawDesc), len(file_remote_kv_proto_rawDesc))) }) return file_remote_kv_proto_rawDescData } @@ -2055,7 +2066,7 @@ func file_remote_kv_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_remote_kv_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_remote_kv_proto_rawDesc), len(file_remote_kv_proto_rawDesc)), NumEnums: 3, NumMessages: 21, NumExtensions: 0, @@ -2067,7 +2078,6 @@ func file_remote_kv_proto_init() { MessageInfos: file_remote_kv_proto_msgTypes, }.Build() File_remote_kv_proto = out.File - file_remote_kv_proto_rawDesc = nil file_remote_kv_proto_goTypes = nil file_remote_kv_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/sentinelproto/sentinel.pb.go b/erigon-lib/gointerfaces/sentinelproto/sentinel.pb.go index 52448fc1155..d84bc2620fa 100644 --- a/erigon-lib/gointerfaces/sentinelproto/sentinel.pb.go +++ b/erigon-lib/gointerfaces/sentinelproto/sentinel.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: p2psentinel/sentinel.proto @@ -12,6 +12,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -803,7 +804,7 @@ func (x *RequestSubscribeExpiry) GetExpiryUnixSecs() uint64 { var File_p2psentinel_sentinel_proto protoreflect.FileDescriptor -var file_p2psentinel_sentinel_proto_rawDesc = []byte{ +var file_p2psentinel_sentinel_proto_rawDesc = string([]byte{ 0x0a, 0x1a, 0x70, 0x32, 0x70, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x2f, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x74, 0x79, @@ -948,16 +949,16 @@ var file_p2psentinel_sentinel_proto_rawDesc = []byte{ 0x73, 0x65, 0x42, 0x1a, 0x5a, 0x18, 0x2e, 0x2f, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x3b, 0x73, 0x65, 0x6e, 0x74, 0x69, 0x6e, 0x65, 0x6c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_p2psentinel_sentinel_proto_rawDescOnce sync.Once - file_p2psentinel_sentinel_proto_rawDescData = file_p2psentinel_sentinel_proto_rawDesc + file_p2psentinel_sentinel_proto_rawDescData []byte ) func file_p2psentinel_sentinel_proto_rawDescGZIP() []byte { file_p2psentinel_sentinel_proto_rawDescOnce.Do(func() { - file_p2psentinel_sentinel_proto_rawDescData = protoimpl.X.CompressGZIP(file_p2psentinel_sentinel_proto_rawDescData) + file_p2psentinel_sentinel_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2psentinel_sentinel_proto_rawDesc), len(file_p2psentinel_sentinel_proto_rawDesc))) }) return file_p2psentinel_sentinel_proto_rawDescData } @@ -1029,7 +1030,7 @@ func file_p2psentinel_sentinel_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_p2psentinel_sentinel_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_p2psentinel_sentinel_proto_rawDesc), len(file_p2psentinel_sentinel_proto_rawDesc)), NumEnums: 0, NumMessages: 13, NumExtensions: 0, @@ -1040,7 +1041,6 @@ func file_p2psentinel_sentinel_proto_init() { MessageInfos: file_p2psentinel_sentinel_proto_msgTypes, }.Build() File_p2psentinel_sentinel_proto = out.File - file_p2psentinel_sentinel_proto_rawDesc = nil file_p2psentinel_sentinel_proto_goTypes = nil file_p2psentinel_sentinel_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/sentryproto/sentry.pb.go b/erigon-lib/gointerfaces/sentryproto/sentry.pb.go index b3408a33d3e..abb6b6e9ecc 100644 --- a/erigon-lib/gointerfaces/sentryproto/sentry.pb.go +++ b/erigon-lib/gointerfaces/sentryproto/sentry.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: p2psentry/sentry.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -1446,7 +1447,7 @@ func (x *AddPeerReply) GetSuccess() bool { var File_p2psentry_sentry_proto protoreflect.FileDescriptor -var file_p2psentry_sentry_proto_rawDesc = []byte{ +var file_p2psentry_sentry_proto_rawDesc = string([]byte{ 0x0a, 0x16, 0x70, 0x32, 0x70, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2f, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, @@ -1694,16 +1695,16 @@ var file_p2psentry_sentry_proto_rawDesc = []byte{ 0x79, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x3b, 0x73, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_p2psentry_sentry_proto_rawDescOnce sync.Once - file_p2psentry_sentry_proto_rawDescData = file_p2psentry_sentry_proto_rawDesc + file_p2psentry_sentry_proto_rawDescData []byte ) func file_p2psentry_sentry_proto_rawDescGZIP() []byte { file_p2psentry_sentry_proto_rawDescOnce.Do(func() { - file_p2psentry_sentry_proto_rawDescData = protoimpl.X.CompressGZIP(file_p2psentry_sentry_proto_rawDescData) + file_p2psentry_sentry_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_p2psentry_sentry_proto_rawDesc), len(file_p2psentry_sentry_proto_rawDesc))) }) return file_p2psentry_sentry_proto_rawDescData } @@ -1816,7 +1817,7 @@ func file_p2psentry_sentry_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_p2psentry_sentry_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_p2psentry_sentry_proto_rawDesc), len(file_p2psentry_sentry_proto_rawDesc)), NumEnums: 4, NumMessages: 23, NumExtensions: 0, @@ -1828,7 +1829,6 @@ func file_p2psentry_sentry_proto_init() { MessageInfos: file_p2psentry_sentry_proto_msgTypes, }.Build() File_p2psentry_sentry_proto = out.File - file_p2psentry_sentry_proto_rawDesc = nil file_p2psentry_sentry_proto_goTypes = nil file_p2psentry_sentry_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/txpoolproto/mining.pb.go b/erigon-lib/gointerfaces/txpoolproto/mining.pb.go index d5ec261bc70..1d605360522 100644 --- a/erigon-lib/gointerfaces/txpoolproto/mining.pb.go +++ b/erigon-lib/gointerfaces/txpoolproto/mining.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: txpool/mining.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -736,7 +737,7 @@ func (x *MiningReply) GetRunning() bool { var File_txpool_mining_proto protoreflect.FileDescriptor -var file_txpool_mining_proto_rawDesc = []byte{ +var file_txpool_mining_proto_rawDesc = string([]byte{ 0x0a, 0x13, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, @@ -831,16 +832,16 @@ var file_txpool_mining_proto_rawDesc = []byte{ 0x6c, 0x79, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x3b, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_txpool_mining_proto_rawDescOnce sync.Once - file_txpool_mining_proto_rawDescData = file_txpool_mining_proto_rawDesc + file_txpool_mining_proto_rawDescData []byte ) func file_txpool_mining_proto_rawDescGZIP() []byte { file_txpool_mining_proto_rawDescOnce.Do(func() { - file_txpool_mining_proto_rawDescData = protoimpl.X.CompressGZIP(file_txpool_mining_proto_rawDescData) + file_txpool_mining_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_txpool_mining_proto_rawDesc), len(file_txpool_mining_proto_rawDesc))) }) return file_txpool_mining_proto_rawDescData } @@ -901,7 +902,7 @@ func file_txpool_mining_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_txpool_mining_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_txpool_mining_proto_rawDesc), len(file_txpool_mining_proto_rawDesc)), NumEnums: 0, NumMessages: 16, NumExtensions: 0, @@ -912,7 +913,6 @@ func file_txpool_mining_proto_init() { MessageInfos: file_txpool_mining_proto_msgTypes, }.Build() File_txpool_mining_proto = out.File - file_txpool_mining_proto_rawDesc = nil file_txpool_mining_proto_goTypes = nil file_txpool_mining_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/txpoolproto/txpool.pb.go b/erigon-lib/gointerfaces/txpoolproto/txpool.pb.go index 9ce25d1be75..2d07e97474e 100644 --- a/erigon-lib/gointerfaces/txpoolproto/txpool.pb.go +++ b/erigon-lib/gointerfaces/txpoolproto/txpool.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: txpool/txpool.proto @@ -13,6 +13,7 @@ import ( emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -875,7 +876,7 @@ func (x *PendingReply_Tx) GetIsLocal() bool { var File_txpool_txpool_proto protoreflect.FileDescriptor -var file_txpool_txpool_proto_rawDesc = []byte{ +var file_txpool_txpool_proto_rawDesc = string([]byte{ 0x0a, 0x13, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x2f, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, @@ -982,16 +983,16 @@ var file_txpool_txpool_proto_rawDesc = []byte{ 0x6c, 0x2e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x16, 0x5a, 0x14, 0x2e, 0x2f, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x3b, 0x74, 0x78, 0x70, 0x6f, 0x6f, 0x6c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_txpool_txpool_proto_rawDescOnce sync.Once - file_txpool_txpool_proto_rawDescData = file_txpool_txpool_proto_rawDesc + file_txpool_txpool_proto_rawDescData []byte ) func file_txpool_txpool_proto_rawDescGZIP() []byte { file_txpool_txpool_proto_rawDescOnce.Do(func() { - file_txpool_txpool_proto_rawDescData = protoimpl.X.CompressGZIP(file_txpool_txpool_proto_rawDescData) + file_txpool_txpool_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_txpool_txpool_proto_rawDesc), len(file_txpool_txpool_proto_rawDesc))) }) return file_txpool_txpool_proto_rawDescData } @@ -1066,7 +1067,7 @@ func file_txpool_txpool_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_txpool_txpool_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_txpool_txpool_proto_rawDesc), len(file_txpool_txpool_proto_rawDesc)), NumEnums: 2, NumMessages: 16, NumExtensions: 0, @@ -1078,7 +1079,6 @@ func file_txpool_txpool_proto_init() { MessageInfos: file_txpool_txpool_proto_msgTypes, }.Build() File_txpool_txpool_proto = out.File - file_txpool_txpool_proto_rawDesc = nil file_txpool_txpool_proto_goTypes = nil file_txpool_txpool_proto_depIdxs = nil } diff --git a/erigon-lib/gointerfaces/typesproto/types.pb.go b/erigon-lib/gointerfaces/typesproto/types.pb.go index 8d04788e6b0..c1739560530 100644 --- a/erigon-lib/gointerfaces/typesproto/types.pb.go +++ b/erigon-lib/gointerfaces/typesproto/types.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.3 +// protoc-gen-go v1.36.4 // protoc v5.29.3 // source: types/types.proto @@ -12,6 +12,7 @@ import ( descriptorpb "google.golang.org/protobuf/types/descriptorpb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -1102,7 +1103,7 @@ var ( var File_types_types_proto protoreflect.FileDescriptor -var file_types_types_proto_rawDesc = []byte{ +var file_types_types_proto_rawDesc = string([]byte{ 0x0a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, @@ -1263,16 +1264,16 @@ var file_types_types_proto_rawDesc = []byte{ 0x61, 0x74, 0x63, 0x68, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x14, 0x5a, 0x12, 0x2e, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +}) var ( file_types_types_proto_rawDescOnce sync.Once - file_types_types_proto_rawDescData = file_types_types_proto_rawDesc + file_types_types_proto_rawDescData []byte ) func file_types_types_proto_rawDescGZIP() []byte { file_types_types_proto_rawDescOnce.Do(func() { - file_types_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_types_types_proto_rawDescData) + file_types_types_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_types_types_proto_rawDesc), len(file_types_types_proto_rawDesc))) }) return file_types_types_proto_rawDescData } @@ -1338,7 +1339,7 @@ func file_types_types_proto_init() { out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_types_types_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_types_proto_rawDesc), len(file_types_types_proto_rawDesc)), NumEnums: 0, NumMessages: 15, NumExtensions: 3, @@ -1350,7 +1351,6 @@ func file_types_types_proto_init() { ExtensionInfos: file_types_types_proto_extTypes, }.Build() File_types_types_proto = out.File - file_types_types_proto_rawDesc = nil file_types_types_proto_goTypes = nil file_types_types_proto_depIdxs = nil } diff --git a/eth/stagedsync/exec3.go b/eth/stagedsync/exec3.go index 2d6995345b0..98bbe1170f0 100644 --- a/eth/stagedsync/exec3.go +++ b/eth/stagedsync/exec3.go @@ -512,7 +512,7 @@ Loop: if err != nil { return err } - accumulator.StartChange(b.NumberU64(), b.Hash(), txs, false) + accumulator.StartChange(header, txs, false) } rules := chainConfig.Rules(blockNum, b.Time()) diff --git a/eth/stagedsync/stage_execute.go b/eth/stagedsync/stage_execute.go index c8db035c8ef..9c0c9fda10f 100644 --- a/eth/stagedsync/stage_execute.go +++ b/eth/stagedsync/stage_execute.go @@ -372,11 +372,18 @@ func unwindExecutionStage(u *UnwindState, s *StageState, txc wrap.TxContainer, c if !ok { return fmt.Errorf("canonical hash not found %d", u.UnwindPoint) } + header, err := cfg.blockReader.HeaderByHash(ctx, txc.Tx, hash) + if err != nil { + return fmt.Errorf("read canonical header of unwind point: %w", err) + } + if header == nil { + return fmt.Errorf("canonical header for unwind point not found: %s", hash) + } txs, err := cfg.blockReader.RawTransactions(ctx, txc.Tx, u.UnwindPoint, s.BlockNumber) if err != nil { return err } - accumulator.StartChange(u.UnwindPoint, hash, txs, true) + accumulator.StartChange(header, txs, true) } return unwindExec3(u, s, txc, ctx, cfg.blockReader, accumulator, logger) diff --git a/turbo/shards/state_change_accumulator.go b/turbo/shards/state_change_accumulator.go index 1e0e57a3cbf..76dabcae21c 100644 --- a/turbo/shards/state_change_accumulator.go +++ b/turbo/shards/state_change_accumulator.go @@ -22,6 +22,7 @@ import ( libcommon "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/gointerfaces" remote "github.com/erigontech/erigon-lib/gointerfaces/remoteproto" + "github.com/erigontech/erigon/core/types" ) // Accumulator collects state changes in a form that can then be delivered to the RPC daemon @@ -63,11 +64,12 @@ func (a *Accumulator) SetStateID(stateID uint64) { } // StartChange begins accumulation of changes for a new block -func (a *Accumulator) StartChange(blockHeight uint64, blockHash libcommon.Hash, txs [][]byte, unwind bool) { +func (a *Accumulator) StartChange(h *types.Header, txs [][]byte, unwind bool) { a.changes = append(a.changes, &remote.StateChange{}) a.latestChange = a.changes[len(a.changes)-1] - a.latestChange.BlockHeight = blockHeight - a.latestChange.BlockHash = gointerfaces.ConvertHashToH256(blockHash) + a.latestChange.BlockHeight = h.Number.Uint64() + a.latestChange.BlockHash = gointerfaces.ConvertHashToH256(h.Hash()) + a.latestChange.BlockTime = h.Time if unwind { a.latestChange.Direction = remote.Direction_UNWIND } else { diff --git a/turbo/stages/stageloop.go b/turbo/stages/stageloop.go index 5b8f86302eb..a956ebbbb13 100644 --- a/turbo/stages/stageloop.go +++ b/turbo/stages/stageloop.go @@ -478,7 +478,7 @@ func (h *Hook) sendNotifications(tx kv.Tx, finishStageBeforeSync uint64) error { currentHeader := rawdb.ReadCurrentHeader(tx) if (h.notifications.Accumulator != nil) && (currentHeader != nil) { if currentHeader.Number.Uint64() == 0 { - h.notifications.Accumulator.StartChange(0, currentHeader.Hash(), nil, false) + h.notifications.Accumulator.StartChange(currentHeader, nil, false) } pendingBaseFee := misc.CalcBaseFee(h.chainConfig, currentHeader) From 66001ff96440cb685d51251664171be3192003d8 Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:27:23 +0000 Subject: [PATCH 19/42] shutter: add encrypted and decrypted txn pools (#13864) closes https://github.com/erigontech/erigon/issues/13386 --- txnprovider/shutter/block_listener.go | 12 +- txnprovider/shutter/config.go | 23 +- txnprovider/shutter/decrypted_txns_pool.go | 103 ++++++ .../shutter/decryption_keys_processor.go | 282 ++++++++++++++- .../shutter/decryption_keys_signature_data.go | 26 +- .../decryption_keys_signature_data_test.go | 6 +- .../shutter/decryption_keys_validator.go | 8 +- .../shutter/encrypted_txn_submission.go | 75 ++++ txnprovider/shutter/encrypted_txns_pool.go | 332 ++++++++++++++++++ txnprovider/shutter/eon.go | 17 +- txnprovider/shutter/eon_tracker.go | 15 +- .../eon_key_generation_mock_data.go | 4 +- .../identity_preimage_mock_data.go | 10 +- txnprovider/shutter/pool.go | 16 +- 14 files changed, 862 insertions(+), 67 deletions(-) create mode 100644 txnprovider/shutter/decrypted_txns_pool.go create mode 100644 txnprovider/shutter/encrypted_txn_submission.go create mode 100644 txnprovider/shutter/encrypted_txns_pool.go diff --git a/txnprovider/shutter/block_listener.go b/txnprovider/shutter/block_listener.go index 92ba93bddf6..1a22cc30f51 100644 --- a/txnprovider/shutter/block_listener.go +++ b/txnprovider/shutter/block_listener.go @@ -25,8 +25,10 @@ import ( ) type BlockEvent struct { - BlockNum uint64 - Unwind bool + LatestBlockNum uint64 + LatestBlockTime uint64 + BlocksBatchLen uint64 + Unwind bool } type BlockListener struct { @@ -65,8 +67,10 @@ func (bl BlockListener) Run(ctx context.Context) error { latestChange := batch.ChangeBatch[len(batch.ChangeBatch)-1] blockEvent := BlockEvent{ - BlockNum: latestChange.BlockHeight, - Unwind: latestChange.Direction == remoteproto.Direction_UNWIND, + LatestBlockNum: latestChange.BlockHeight, + LatestBlockTime: latestChange.BlockTime, + BlocksBatchLen: uint64(len(batch.ChangeBatch)), + Unwind: latestChange.Direction == remoteproto.Direction_UNWIND, } bl.events.NotifySync(blockEvent) diff --git a/txnprovider/shutter/config.go b/txnprovider/shutter/config.go index d912c0e19c6..cfe75ff1897 100644 --- a/txnprovider/shutter/config.go +++ b/txnprovider/shutter/config.go @@ -19,17 +19,20 @@ package shutter import ( "crypto/ecdsa" + "github.com/holiman/uint256" "github.com/libp2p/go-libp2p/core/peer" "github.com/multiformats/go-multiaddr" "github.com/erigontech/erigon-lib/chain/networkname" "github.com/erigontech/erigon/cl/clparams" + "github.com/erigontech/erigon/params" ) type Config struct { P2pConfig Enabled bool InstanceId uint64 + ChainId *uint256.Int BeaconChainGenesisTimestamp uint64 SecondsPerSlot uint64 SequencerContractAddress string @@ -38,6 +41,9 @@ type Config struct { KeyperSetManagerContractAddress string MaxNumKeysPerMessage uint64 ReorgDepthAwareness uint64 + MaxPooledEncryptedTxns int + EncryptedGasLimit uint64 + EncryptedTxnsLookBackDistance uint64 } type P2pConfig struct { @@ -80,6 +86,7 @@ var ( chiadoConfig = Config{ Enabled: true, InstanceId: 102_000, + ChainId: uint256.MustFromBig(params.ChiadoChainConfig.ChainID), BeaconChainGenesisTimestamp: 1665396300, SecondsPerSlot: clparams.BeaconConfigs[clparams.ChiadoNetwork].SecondsPerSlot, SequencerContractAddress: "0x2aD8E2feB0ED5b2EC8e700edB725f120576994ed", @@ -88,6 +95,9 @@ var ( KeyperSetManagerContractAddress: "0xC4DE9FAf4ec882b33dA0162CBE628B0D8205D0c0", MaxNumKeysPerMessage: defaultMaxNumKeysPerMessage, ReorgDepthAwareness: defaultReorgDepthAwarenessEpochs * clparams.BeaconConfigs[clparams.ChiadoNetwork].SlotsPerEpoch, + MaxPooledEncryptedTxns: defaultMaxPooledEncryptedTxns, + EncryptedGasLimit: defaultEncryptedGasLimit, + EncryptedTxnsLookBackDistance: defaultEncryptedTxnsLookBackDistance, P2pConfig: P2pConfig{ ListenPort: defaultP2PListenPort, BootstrapNodes: []string{ @@ -100,6 +110,7 @@ var ( gnosisConfig = Config{ Enabled: true, InstanceId: 1_000, + ChainId: uint256.MustFromBig(params.GnosisChainConfig.ChainID), BeaconChainGenesisTimestamp: 1638993340, SecondsPerSlot: clparams.BeaconConfigs[clparams.GnosisNetwork].SecondsPerSlot, SequencerContractAddress: "0xc5C4b277277A1A8401E0F039dfC49151bA64DC2E", @@ -108,6 +119,9 @@ var ( KeyperSetManagerContractAddress: "0x7C2337f9bFce19d8970661DA50dE8DD7d3D34abb", MaxNumKeysPerMessage: defaultMaxNumKeysPerMessage, ReorgDepthAwareness: defaultReorgDepthAwarenessEpochs * clparams.BeaconConfigs[clparams.GnosisNetwork].SlotsPerEpoch, + MaxPooledEncryptedTxns: defaultMaxPooledEncryptedTxns, + EncryptedGasLimit: defaultEncryptedGasLimit, + EncryptedTxnsLookBackDistance: defaultEncryptedTxnsLookBackDistance, P2pConfig: P2pConfig{ ListenPort: defaultP2PListenPort, BootstrapNodes: []string{ @@ -119,7 +133,10 @@ var ( ) const ( - defaultP2PListenPort = 23_102 - defaultMaxNumKeysPerMessage = 500 - defaultReorgDepthAwarenessEpochs = 3 + defaultP2PListenPort = 23_102 + defaultMaxNumKeysPerMessage = 500 + defaultReorgDepthAwarenessEpochs = 3 + defaultMaxPooledEncryptedTxns = 10_000 + defaultEncryptedGasLimit = 10_000_000 + defaultEncryptedTxnsLookBackDistance = 128 ) diff --git a/txnprovider/shutter/decrypted_txns_pool.go b/txnprovider/shutter/decrypted_txns_pool.go new file mode 100644 index 00000000000..47f395eaeb2 --- /dev/null +++ b/txnprovider/shutter/decrypted_txns_pool.go @@ -0,0 +1,103 @@ +// Copyright 2025 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 shutter + +import ( + "context" + "sync" + + "github.com/erigontech/erigon/core/types" +) + +type DecryptionMark struct { + Slot uint64 + Eon EonIndex +} + +type TxnBatch struct { + Transactions []types.Transaction + TotalGasLimit uint64 +} + +type DecryptedTxnsPool struct { + mu *sync.Mutex + decryptedTxns map[DecryptionMark]TxnBatch + decryptionCond *sync.Cond +} + +func NewDecryptedTxnsPool() *DecryptedTxnsPool { + mu := sync.Mutex{} + return &DecryptedTxnsPool{ + mu: &mu, + decryptedTxns: make(map[DecryptionMark]TxnBatch), + decryptionCond: sync.NewCond(&mu), + } +} + +func (p *DecryptedTxnsPool) Wait(ctx context.Context, mark DecryptionMark) error { + done := make(chan struct{}) + go func() { + defer close(done) + + p.mu.Lock() + defer p.mu.Unlock() + + for _, ok := p.decryptedTxns[mark]; !ok && ctx.Err() == nil; _, ok = p.decryptedTxns[mark] { + p.decryptionCond.Wait() + } + }() + + select { + case <-ctx.Done(): + // note the below will wake up all waiters prematurely, but thanks to the for loop condition + // in the waiting goroutine the ones that still need to wait will go back to sleep + p.decryptionCond.Broadcast() + case <-done: + // no-op + } + + return ctx.Err() +} + +func (p *DecryptedTxnsPool) DecryptedTxns(mark DecryptionMark) (TxnBatch, bool) { + p.mu.Lock() + defer p.mu.Unlock() + txnBatch, ok := p.decryptedTxns[mark] + return txnBatch, ok +} + +func (p *DecryptedTxnsPool) AddDecryptedTxns(mark DecryptionMark, txnBatch TxnBatch) { + p.mu.Lock() + defer p.mu.Unlock() + p.decryptedTxns[mark] = txnBatch + p.decryptionCond.Broadcast() +} + +func (p *DecryptedTxnsPool) DeleteDecryptedTxnsUpToSlot(slot uint64) uint64 { + p.mu.Lock() + defer p.mu.Unlock() + + var deletions uint64 + for mark := range p.decryptedTxns { + if mark.Slot <= slot { + deletions++ + delete(p.decryptedTxns, mark) + } + } + + return deletions +} diff --git a/txnprovider/shutter/decryption_keys_processor.go b/txnprovider/shutter/decryption_keys_processor.go index 0f3d595ecd3..9ad3c3be254 100644 --- a/txnprovider/shutter/decryption_keys_processor.go +++ b/txnprovider/shutter/decryption_keys_processor.go @@ -17,21 +17,54 @@ package shutter import ( + "bytes" "context" + "errors" + "fmt" + "sync/atomic" + mapset "github.com/deckarep/golang-set/v2" + "golang.org/x/sync/errgroup" + + libcommon "github.com/erigontech/erigon-lib/common" "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon/core/types" + "github.com/erigontech/erigon/eth/ethconfig/estimate" + shuttercrypto "github.com/erigontech/erigon/txnprovider/shutter/internal/crypto" "github.com/erigontech/erigon/txnprovider/shutter/internal/proto" + "github.com/erigontech/erigon/txnprovider/txpool" ) type DecryptionKeysProcessor struct { - logger log.Logger - queue chan *proto.DecryptionKeys + logger log.Logger + config Config + encryptedTxnsPool *EncryptedTxnsPool + decryptedTxnsPool *DecryptedTxnsPool + blockListener BlockListener + slotCalculator SlotCalculator + txnParseCtx *txpool.TxnParseContext + queue chan *proto.DecryptionKeys + processed mapset.Set[ProcessedMark] } -func NewDecryptionKeysProcessor(logger log.Logger) DecryptionKeysProcessor { +func NewDecryptionKeysProcessor( + logger log.Logger, + config Config, + encryptedTxnsPool *EncryptedTxnsPool, + decryptedTxnsPool *DecryptedTxnsPool, + blockListener BlockListener, + slotCalculator SlotCalculator, +) DecryptionKeysProcessor { return DecryptionKeysProcessor{ - logger: logger, - queue: make(chan *proto.DecryptionKeys), + logger: logger, + config: config, + encryptedTxnsPool: encryptedTxnsPool, + decryptedTxnsPool: decryptedTxnsPool, + blockListener: blockListener, + slotCalculator: slotCalculator, + txnParseCtx: txpool.NewTxnParseContext(*config.ChainId).ChainIDRequired(), + queue: make(chan *proto.DecryptionKeys), + processed: mapset.NewSet[ProcessedMark](), } } @@ -43,21 +76,240 @@ func (dkp DecryptionKeysProcessor) Run(ctx context.Context) error { defer dkp.logger.Info("decryption keys processor stopped") dkp.logger.Info("running decryption keys processor") + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { return dkp.processKeys(ctx) }) + eg.Go(func() error { return dkp.cleanupLoop(ctx) }) + return eg.Wait() +} + +func (dkp DecryptionKeysProcessor) processKeys(ctx context.Context) error { for { select { + case <-ctx.Done(): + return ctx.Err() case msg := <-dkp.queue: - dkp.logger.Debug( - "processing decryption keys message", - "instanceId", msg.InstanceId, - "eon", msg.Eon, - "slot", msg.GetGnosis().Slot, - "txPointer", msg.GetGnosis().TxPointer, - ) - // - // TODO process msg - // + err := dkp.process(msg) + if err != nil { + return err + } + } + } +} + +func (dkp DecryptionKeysProcessor) process(msg *proto.DecryptionKeys) error { + dkp.logger.Debug( + "processing decryption keys message", + "instanceId", msg.InstanceId, + "eon", msg.Eon, + "slot", msg.GetGnosis().Slot, + "txPointer", msg.GetGnosis().TxPointer, + "keys", len(msg.Keys), + ) + + keys := msg.Keys[1:] // skip placeholder (we can safely do this because msg has already been validated) + eonIndex := EonIndex(msg.Eon) + from := TxnIndex(msg.GetGnosis().TxPointer) + to := from + TxnIndex(len(keys)) // [from,to) + processedMark := ProcessedMark{Slot: msg.GetGnosis().Slot, Eon: eonIndex, From: from, To: to} + if dkp.processed.Contains(processedMark) { + dkp.logger.Debug( + "skipping decryption keys message - already processed", + "slot", processedMark.Slot, + "eonIndex", processedMark.Eon, + "from", processedMark.From, + "to", processedMark.To, + ) + return nil + } + + encryptedTxns, err := dkp.encryptedTxnsPool.Txns(eonIndex, from, to, dkp.config.EncryptedGasLimit) + if err != nil { + return err + } + + txnIndexToKey := make(map[TxnIndex]*proto.Key, len(keys)) + for i, key := range keys { + txnIndexToKey[from+TxnIndex(i)] = key + } + + var eg errgroup.Group + eg.SetLimit(estimate.AlmostAllCPUs()) + txns := make([]types.Transaction, len(encryptedTxns)) + totalGasLimit := atomic.Uint64{} + for i, encryptedTxn := range encryptedTxns { + eg.Go(func() error { + txn, err := dkp.decryptTxn(txnIndexToKey, encryptedTxn) + if err != nil { + dkp.logger.Warn( + "failed to decrypt transaction - skipping", + "slot", msg.GetGnosis().Slot, + "eonIndex", msg.Eon, + "txnIndex", encryptedTxn.TxnIndex, + "err", err, + ) + // we do not return err here since as per protocol we skip bad decryption + // we also do not want to interrupt other decryption goroutines + return nil + } + + txns[i] = txn + totalGasLimit.Add(encryptedTxn.GasLimit.Uint64()) + return nil + }) + } + + err = eg.Wait() + if err != nil { + return err + } + + // txns that didn't pass decryption and other checks will be left nil -> filter those out + filteredTxns := make([]types.Transaction, 0, len(txns)) + for _, txn := range txns { + if txn == nil { + continue + } + + filteredTxns = append(filteredTxns, txn) + } + + decryptionMark := DecryptionMark{Slot: msg.GetGnosis().Slot, Eon: eonIndex} + txnBatch := TxnBatch{Transactions: filteredTxns, TotalGasLimit: totalGasLimit.Load()} + dkp.decryptedTxnsPool.AddDecryptedTxns(decryptionMark, txnBatch) + dkp.processed.Add(processedMark) + return nil +} + +func (dkp DecryptionKeysProcessor) decryptTxn(keys map[TxnIndex]*proto.Key, sub EncryptedTxnSubmission) (types.Transaction, error) { + key, ok := keys[sub.TxnIndex] + if !ok { + return nil, fmt.Errorf("key not found for txn index %d", sub.TxnIndex) + } + + epochSecretKey, err := EpochSecretKeyFromBytes(key.Key) + if err != nil { + return nil, fmt.Errorf("failed to decode epoch secret key during txn decryption: %w", err) + } + + submissionIdentityPreimage := sub.IdentityPreimageBytes() + if !bytes.Equal(key.IdentityPreimage, submissionIdentityPreimage) { + return nil, identityPreimageMismatchErr(key.IdentityPreimage, submissionIdentityPreimage) + } + + encryptedMessage := new(shuttercrypto.EncryptedMessage) + err = encryptedMessage.Unmarshal(sub.EncryptedTransaction) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal encrypted message: %w", err) + } + + decryptedMessage, err := encryptedMessage.Decrypt(epochSecretKey) + if err != nil { + return nil, fmt.Errorf("failed to decrypt message: %w", err) + } + + var txnSlot txpool.TxnSlot + var sender libcommon.Address + _, err = dkp.txnParseCtx.ParseTransaction(decryptedMessage, 0, &txnSlot, sender[:], true, true, nil) + if err != nil { + return nil, fmt.Errorf("failed to parse decrypted transaction: %w", err) + } + + txn, err := types.DecodeWrappedTransaction(txnSlot.Rlp) + if err != nil { + return nil, fmt.Errorf("failed to decode transaction: %w", err) + } + + if txn.Type() == types.BlobTxType { + return nil, errors.New("blob txns not allowed in shutter") + } + + if subGasLimit := sub.GasLimit.Uint64(); txn.GetGas() != subGasLimit { + return nil, fmt.Errorf("txn gas limit mismatch: txn=%d, encryptedTxnSubmission=%d", txn.GetGas(), subGasLimit) + } + + return txn, nil +} + +func (dkp DecryptionKeysProcessor) cleanupLoop(ctx context.Context) error { + blockEventC := make(chan BlockEvent) + unregister := dkp.blockListener.RegisterObserver(func(event BlockEvent) { + select { + case <-ctx.Done(): + case blockEventC <- event: + } + }) + defer unregister() + + for { + select { case <-ctx.Done(): return ctx.Err() + case blockEvent := <-blockEventC: + err := dkp.processBlockEventCleanup(blockEvent) + if err != nil { + return err + } } } } + +func (dkp DecryptionKeysProcessor) processBlockEventCleanup(blockEvent BlockEvent) error { + slot, err := dkp.slotCalculator.CalcSlot(blockEvent.LatestBlockTime) + if err != nil { + return err + } + + // decryptedTxnsPool is not re-org aware since it is slot based - we can clean it up straight away + decryptedTxnsDeletions := dkp.decryptedTxnsPool.DeleteDecryptedTxnsUpToSlot(slot) + if decryptedTxnsDeletions > 0 { + dkp.logger.Debug("cleaned up decrypted txns up to", "slot", slot, "deletions", decryptedTxnsDeletions) + } + + // encryptedTxnPool on the other hand may be sensitive to re-orgs - clean it up with some delay + var cleanUpToSlot uint64 + if slot > dkp.config.ReorgDepthAwareness { + cleanUpToSlot = slot - dkp.config.ReorgDepthAwareness + } + + var cleanUpMarks []ProcessedMark + dkp.processed.Each(func(mark ProcessedMark) bool { + if mark.Slot <= cleanUpToSlot { + cleanUpMarks = append(cleanUpMarks, mark) + } + return false // continue, want to check all + }) + + for _, mark := range cleanUpMarks { + dkp.processed.Remove(mark) + dkp.encryptedTxnsPool.DeleteUpTo(mark.Eon, mark.To+1) + } + + return nil +} + +func identityPreimageMismatchErr(keyIpBytes, submissionIpBytes []byte) error { + err := errors.New("identity preimage mismatch") + + keyIp, ipErr := IdentityPreimageFromBytes(keyIpBytes) + if ipErr != nil { + err = fmt.Errorf("%w: keyIp=%s", err, ipErr) + } else { + err = fmt.Errorf("%w: keyIp=%s", err, keyIp) + } + + submissionIp, ipErr := IdentityPreimageFromBytes(submissionIpBytes) + if ipErr != nil { + err = fmt.Errorf("%w: submissionIp=%s", err, ipErr) + } else { + err = fmt.Errorf("%w: submissionIp=%s", err, submissionIp) + } + + return err +} + +type ProcessedMark struct { + Slot uint64 + Eon EonIndex + From TxnIndex + To TxnIndex +} diff --git a/txnprovider/shutter/decryption_keys_signature_data.go b/txnprovider/shutter/decryption_keys_signature_data.go index 59eb6fa73d5..e89d0ee7d7e 100644 --- a/txnprovider/shutter/decryption_keys_signature_data.go +++ b/txnprovider/shutter/decryption_keys_signature_data.go @@ -42,47 +42,45 @@ const ( type IdentityPreimage [identityPreimageSize]byte -func (ip *IdentityPreimage) EncodingSizeSSZ() int { +func (ip IdentityPreimage) EncodingSizeSSZ() int { return identityPreimageSize } -func (ip *IdentityPreimage) EncodeSSZ(dst []byte) ([]byte, error) { +func (ip IdentityPreimage) EncodeSSZ(dst []byte) ([]byte, error) { return append(dst, ip[:]...), nil } -func (ip *IdentityPreimage) DecodeSSZ(buf []byte, _ int) error { +func (ip IdentityPreimage) DecodeSSZ(buf []byte, _ int) error { if len(buf) != identityPreimageSize { return fmt.Errorf("%w: len=%d", ErrIncorrectIdentityPreimageSize, len(ip)) } - var newIp IdentityPreimage - copy(newIp[:], buf) - *ip = newIp + copy(ip[:], buf) return nil } -func (ip *IdentityPreimage) Clone() clonable.Clonable { +func (ip IdentityPreimage) Clone() clonable.Clonable { clone := IdentityPreimage(slices.Clone(ip[:])) return &clone } -func (ip *IdentityPreimage) HashSSZ() ([32]byte, error) { +func (ip IdentityPreimage) HashSSZ() ([32]byte, error) { return merkletree.BytesRoot(ip[:]) } -func (ip *IdentityPreimage) String() string { +func (ip IdentityPreimage) String() string { return hexutil.Encode(ip[:]) } -func IdentityPreimageFromSSZ(b []byte) (*IdentityPreimage, error) { - ip := new(IdentityPreimage) +func IdentityPreimageFromBytes(b []byte) (IdentityPreimage, error) { + var ip IdentityPreimage err := ip.DecodeSSZ(b, 0) return ip, err } -type IdentityPreimages []*IdentityPreimage +type IdentityPreimages []IdentityPreimage -func (ips IdentityPreimages) ToListSSZ() *solid.ListSSZ[*IdentityPreimage] { +func (ips IdentityPreimages) ToListSSZ() *solid.ListSSZ[IdentityPreimage] { return solid.NewStaticListSSZFromList(ips, identityPreimagesLimit, identityPreimageSize) } @@ -91,7 +89,7 @@ type DecryptionKeysSignatureData struct { Eon EonIndex Slot uint64 TxnPointer uint64 - IdentityPreimages *solid.ListSSZ[*IdentityPreimage] + IdentityPreimages *solid.ListSSZ[IdentityPreimage] } func (d DecryptionKeysSignatureData) HashSSZ() ([32]byte, error) { diff --git a/txnprovider/shutter/decryption_keys_signature_data_test.go b/txnprovider/shutter/decryption_keys_signature_data_test.go index ba7b98fc2b7..d87034cc11b 100644 --- a/txnprovider/shutter/decryption_keys_signature_data_test.go +++ b/txnprovider/shutter/decryption_keys_signature_data_test.go @@ -14,17 +14,17 @@ func TestIdentityPreimageEncodeDecodeSSZ(t *testing.T) { ip := testhelpers.Uint64ToIdentityPreimage(t, 123) buf, err := ip.EncodeSSZ(nil) require.NoError(t, err) - ip2, err := shutter.IdentityPreimageFromSSZ(buf) + ip2, err := shutter.IdentityPreimageFromBytes(buf) require.NoError(t, err) require.Equal(t, ip, ip2) } func TestIdentityPreimageDecodeSSZWithInvalidLength(t *testing.T) { buf := make([]byte, 39) - _, err := shutter.IdentityPreimageFromSSZ(buf) + _, err := shutter.IdentityPreimageFromBytes(buf) require.ErrorIs(t, err, shutter.ErrIncorrectIdentityPreimageSize) buf = make([]byte, 64) - _, err = shutter.IdentityPreimageFromSSZ(buf) + _, err = shutter.IdentityPreimageFromBytes(buf) require.ErrorIs(t, err, shutter.ErrIncorrectIdentityPreimageSize) } diff --git a/txnprovider/shutter/decryption_keys_validator.go b/txnprovider/shutter/decryption_keys_validator.go index b6b76f27086..b2d0f1ada1a 100644 --- a/txnprovider/shutter/decryption_keys_validator.go +++ b/txnprovider/shutter/decryption_keys_validator.go @@ -160,7 +160,7 @@ func (v DecryptionKeysValidator) validateSignatures(msg *proto.DecryptionKeys, e identityPreimages := make(IdentityPreimages, len(msg.Keys)) for i, k := range msg.Keys { - ip, err := IdentityPreimageFromSSZ(k.IdentityPreimage) + ip, err := IdentityPreimageFromBytes(k.IdentityPreimage) if err != nil { return err } @@ -210,11 +210,11 @@ func (v DecryptionKeysValidator) validateKeys(msg *proto.DecryptionKeys, eon Eon } for _, key := range msg.Keys { - eonSecretKey, err := EonSecretKeyFromBytes(key.Key) + epochSecretKey, err := EpochSecretKeyFromBytes(key.Key) if err != nil { return fmt.Errorf("issue converting eon secret key for identity: %w: %d", err, key.IdentityPreimage) } - ok, err := crypto.VerifyEpochSecretKey(eonSecretKey, &eonPublicKey, key.IdentityPreimage) + ok, err := crypto.VerifyEpochSecretKey(epochSecretKey, eonPublicKey, key.IdentityPreimage) if err != nil { return err } @@ -224,7 +224,7 @@ func (v DecryptionKeysValidator) validateKeys(msg *proto.DecryptionKeys, eon Eon // not valid fullErr := fmt.Errorf("verification of eon secret key failed: %w", ErrInvalidKey) - ip, err := IdentityPreimageFromSSZ(key.IdentityPreimage) + ip, err := IdentityPreimageFromBytes(key.IdentityPreimage) if err != nil { return fmt.Errorf("%w: %w", fullErr, err) } diff --git a/txnprovider/shutter/encrypted_txn_submission.go b/txnprovider/shutter/encrypted_txn_submission.go new file mode 100644 index 00000000000..783b056465d --- /dev/null +++ b/txnprovider/shutter/encrypted_txn_submission.go @@ -0,0 +1,75 @@ +// Copyright 2025 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 . + +//go:build !abigen + +package shutter + +import ( + "math/big" + + libcommon "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon/txnprovider/shutter/internal/contracts" +) + +type TxnIndex uint64 + +// EncryptedTxnSubmission mimics contracts.SequencerTransactionSubmitted but without the "Raw" attribute to save memory. +type EncryptedTxnSubmission struct { + EonIndex EonIndex + TxnIndex TxnIndex + IdentityPrefix [32]byte + Sender libcommon.Address + EncryptedTransaction []byte + GasLimit *big.Int + BlockNum uint64 +} + +func (ets EncryptedTxnSubmission) IdentityPreimageBytes() []byte { + buf := make([]byte, len(ets.IdentityPrefix)+len(ets.Sender)) + copy(buf[:len(ets.IdentityPrefix)], ets.IdentityPrefix[:]) + copy(buf[len(ets.IdentityPrefix):], ets.Sender.Bytes()) + return buf +} + +func EncryptedTxnSubmissionFromLogEvent(event *contracts.SequencerTransactionSubmitted) EncryptedTxnSubmission { + return EncryptedTxnSubmission{ + EonIndex: EonIndex(event.Eon), + TxnIndex: TxnIndex(event.TxIndex), + IdentityPrefix: event.IdentityPrefix, + Sender: event.Sender, + EncryptedTransaction: event.EncryptedTransaction, + GasLimit: event.GasLimit, + BlockNum: event.Raw.BlockNumber, + } +} + +func EncryptedTxnSubmissionLess(a, b EncryptedTxnSubmission) bool { + if a.EonIndex < b.EonIndex { + return true + } + + if a.EonIndex == b.EonIndex && a.TxnIndex <= b.TxnIndex { + return true + } + + return false +} + +func EncryptedTxnSubmissionsAreConsecutive(a, b EncryptedTxnSubmission) bool { + return (a.EonIndex == b.EonIndex && a.TxnIndex+1 == b.TxnIndex) || + (a.EonIndex+1 == b.EonIndex && b.TxnIndex == 0) +} diff --git a/txnprovider/shutter/encrypted_txns_pool.go b/txnprovider/shutter/encrypted_txns_pool.go new file mode 100644 index 00000000000..9db03843df8 --- /dev/null +++ b/txnprovider/shutter/encrypted_txns_pool.go @@ -0,0 +1,332 @@ +// Copyright 2025 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 . + +//go:build !abigen + +package shutter + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/google/btree" + "golang.org/x/sync/errgroup" + + libcommon "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon/accounts/abi/bind" + "github.com/erigontech/erigon/txnprovider/shutter/internal/contracts" +) + +type EncryptedTxnsPool struct { + logger log.Logger + config Config + sequencerContract *contracts.Sequencer + blockListener BlockListener + mu sync.RWMutex + submissions *btree.BTreeG[EncryptedTxnSubmission] + initialLoadDone chan struct{} +} + +func NewEncryptedTxnsPool(logger log.Logger, config Config, cb bind.ContractBackend, bl BlockListener) *EncryptedTxnsPool { + sequencerContractAddress := libcommon.HexToAddress(config.SequencerContractAddress) + sequencerContract, err := contracts.NewSequencer(sequencerContractAddress, cb) + if err != nil { + panic(fmt.Errorf("failed to create shutter sequencer contract: %w", err)) + } + + return &EncryptedTxnsPool{ + logger: logger, + config: config, + sequencerContract: sequencerContract, + blockListener: bl, + submissions: btree.NewG[EncryptedTxnSubmission](32, EncryptedTxnSubmissionLess), + initialLoadDone: make(chan struct{}), + } +} + +func (etp *EncryptedTxnsPool) Run(ctx context.Context) error { + defer etp.logger.Info("encrypted txns pool stopped") + etp.logger.Info("running encrypted txns pool") + + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { return etp.watchSubmissions(ctx) }) + eg.Go(func() error { return etp.loadSubmissionsOnInit(ctx) }) + return eg.Wait() +} + +func (etp *EncryptedTxnsPool) Txns(eon EonIndex, from, to TxnIndex, gasLimit uint64) ([]EncryptedTxnSubmission, error) { + if from > to { + return nil, fmt.Errorf("invalid encrypted txns requests range: %d >= %d", from, to) + } + + etp.mu.RLock() + defer etp.mu.RUnlock() + + fromKey := EncryptedTxnSubmission{EonIndex: eon, TxnIndex: from} + toKey := EncryptedTxnSubmission{EonIndex: eon, TxnIndex: to} + count := to - from + txns := make([]EncryptedTxnSubmission, 0, count) + + var totalGasLimit uint64 + var idxOffset TxnIndex + var err error + etp.submissions.AscendRange(fromKey, toKey, func(item EncryptedTxnSubmission) bool { + newTotalGasLimit := totalGasLimit + item.GasLimit.Uint64() + if newTotalGasLimit > gasLimit { + etp.logger.Warn( + "exceeded gas limit when reading encrypted txns", + "eonIndex", item.EonIndex, + "txnIndex", item.TxnIndex, + "from", from, + "to", to, + "gasLimit", gasLimit, + "totalGasLimit", totalGasLimit, + "newTotalGasLimit", newTotalGasLimit, + ) + return false // break + } + + totalGasLimit = newTotalGasLimit + nextTxnIndex := from + idxOffset + if item.TxnIndex < nextTxnIndex { + // this should never happen - highlights bug in the logic somewhere + err = fmt.Errorf("unexpected item txn index lt next txn index: %d < %d", item.TxnIndex, nextTxnIndex) + return false // break + } + + if item.TxnIndex > nextTxnIndex { + etp.logger.Warn( + "encrypted txn gap when reading encrypted txns", + "gapFrom", nextTxnIndex, + "gapTo", item.TxnIndex, + "eonIndex", item.EonIndex, + "from", from, + "to", to, + ) + idxOffset += item.TxnIndex - nextTxnIndex + 1 + return true // continue + } + + txns = append(txns, item) + return true // continue + }) + + return txns, err +} + +func (etp *EncryptedTxnsPool) DeleteUpTo(eon EonIndex, to TxnIndex) { + etp.mu.Lock() + defer etp.mu.Unlock() + + var toDelete []EncryptedTxnSubmission + pivot := EncryptedTxnSubmission{EonIndex: eon, TxnIndex: to} + etp.submissions.AscendLessThan(pivot, func(item EncryptedTxnSubmission) bool { + toDelete = append(toDelete, item) + return true + }) + + if len(toDelete) == 0 { + return + } + + for _, item := range toDelete { + etp.submissions.Delete(item) + } + + etp.logger.Debug( + "deleted encrypted txns", + "count", len(toDelete), + "fromEon", toDelete[0].EonIndex, + "fromTxn", toDelete[0].TxnIndex, + "toEon", toDelete[len(toDelete)-1].EonIndex, + "toTxn", toDelete[len(toDelete)-1].TxnIndex, + ) +} + +func (etp *EncryptedTxnsPool) watchSubmissions(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-etp.initialLoadDone: + // continue + } + + submissionEventC := make(chan *contracts.SequencerTransactionSubmitted) + submissionEventSub, err := etp.sequencerContract.WatchTransactionSubmitted(&bind.WatchOpts{}, submissionEventC) + if err != nil { + return fmt.Errorf("failed to subscribe to sequencer TransactionSubmitted event: %w", err) + } + + defer submissionEventSub.Unsubscribe() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-submissionEventSub.Err(): + return err + case event := <-submissionEventC: + err := etp.handleEncryptedTxnSubmissionEvent(event) + if err != nil { + return fmt.Errorf("failed to handle encrypted txn submission event: %w", err) + } + } + } +} + +func (etp *EncryptedTxnsPool) handleEncryptedTxnSubmissionEvent(event *contracts.SequencerTransactionSubmitted) error { + etp.mu.Lock() + defer etp.mu.Unlock() + + encryptedTxnSubmission := EncryptedTxnSubmissionFromLogEvent(event) + etp.logger.Debug( + "received encrypted txn submission event", + "eonIndex", encryptedTxnSubmission.EonIndex, + "txnIndex", encryptedTxnSubmission.TxnIndex, + "unwind", event.Raw.Removed, + ) + + if event.Raw.Removed { + etp.submissions.Delete(encryptedTxnSubmission) + return nil + } + + etp.addSubmission(encryptedTxnSubmission) + + lastEncryptedTxnSubmission, ok := etp.submissions.Max() + if ok && !EncryptedTxnSubmissionsAreConsecutive(lastEncryptedTxnSubmission, encryptedTxnSubmission) { + return etp.fillSubmissionGap(lastEncryptedTxnSubmission, encryptedTxnSubmission) + } + + return nil +} + +func (etp *EncryptedTxnsPool) fillSubmissionGap(last, new EncryptedTxnSubmission) error { + fromTxnIndex := last.TxnIndex + 1 + startBlockNum := last.BlockNum + 1 + endBlockNum := new.BlockNum + if endBlockNum > 0 { + endBlockNum-- + } + + etp.logger.Info( + "filling submission gap", + "startBlockNum", startBlockNum, + "endBlockNum", endBlockNum, + "fromTxnIndex", fromTxnIndex, + "toTxnIndex", new.TxnIndex, + ) + + return etp.loadSubmissions(startBlockNum, endBlockNum, stopAtTxnIndexSubmissionsContinuer(fromTxnIndex)) +} + +func (etp *EncryptedTxnsPool) loadSubmissionsOnInit(ctx context.Context) error { + blockEventC := make(chan BlockEvent) + unregister := etp.blockListener.RegisterObserver(func(blockEvent BlockEvent) { + select { + case <-ctx.Done(): + return + case blockEventC <- blockEvent: + // no-op + } + }) + + defer unregister() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case blockEvent := <-blockEventC: + if blockEvent.Unwind { + continue + } + + var start uint64 + end := blockEvent.LatestBlockNum + if end > etp.config.EncryptedTxnsLookBackDistance { + start = end - etp.config.EncryptedTxnsLookBackDistance + } + + etp.logger.Info("loading submissions on init", "start", start, "end", end) + err := etp.loadSubmissions(start, end, alwaysContinueSubmissionsContinuer) + if err != nil { + return fmt.Errorf("failed to load submissions on init: %w", err) + } + + close(etp.initialLoadDone) + return nil // we are done + } + } +} + +func (etp *EncryptedTxnsPool) loadSubmissions(start, end uint64, cont submissionsContinuer) error { + startTime := time.Now() + defer func() { + duration := time.Since(startTime) + etp.logger.Debug("loadSubmissions timing", "start", start, "end", end, "duration", duration) + }() + + opts := bind.FilterOpts{ + Start: start, + End: &end, + } + + submissionsIter, err := etp.sequencerContract.FilterTransactionSubmitted(&opts) + if err != nil { + return fmt.Errorf("failed to filter submissions from sequencer contract: %w", err) + } + + defer func() { + err := submissionsIter.Close() + if err != nil { + etp.logger.Error("failed to close submissions iterator", "err", err) + } + }() + + etp.mu.Lock() + defer etp.mu.Unlock() + for submissionsIter.Next() { + if !cont(submissionsIter.Event) { + return nil + } + + encryptedTxnSubmission := EncryptedTxnSubmissionFromLogEvent(submissionsIter.Event) + etp.addSubmission(encryptedTxnSubmission) + } + + return nil +} + +func (etp *EncryptedTxnsPool) addSubmission(submission EncryptedTxnSubmission) { + etp.submissions.ReplaceOrInsert(submission) + if etp.submissions.Len() > etp.config.MaxPooledEncryptedTxns { + etp.submissions.DeleteMin() + } +} + +type submissionsContinuer func(*contracts.SequencerTransactionSubmitted) bool + +func alwaysContinueSubmissionsContinuer(*contracts.SequencerTransactionSubmitted) bool { + return true +} + +func stopAtTxnIndexSubmissionsContinuer(txnIndex TxnIndex) submissionsContinuer { + return func(event *contracts.SequencerTransactionSubmitted) bool { + return TxnIndex(event.TxIndex) >= txnIndex + } +} diff --git a/txnprovider/shutter/eon.go b/txnprovider/shutter/eon.go index 12bf1469c69..9bb480f7254 100644 --- a/txnprovider/shutter/eon.go +++ b/txnprovider/shutter/eon.go @@ -44,8 +44,8 @@ func (e Eon) KeyperAt(index uint64) (libcommon.Address, error) { return e.Members[index], nil } -func (e Eon) PublicKey() (crypto.EonPublicKey, error) { - eonPublicKey := crypto.EonPublicKey{} +func (e Eon) PublicKey() (*crypto.EonPublicKey, error) { + eonPublicKey := new(crypto.EonPublicKey) err := eonPublicKey.Unmarshal(e.Key) return eonPublicKey, err } @@ -54,13 +54,8 @@ func EonLess(a, b Eon) bool { return a.Index < b.Index } -type EonSecretKey = crypto.EpochSecretKey - -func EonSecretKeyFromBytes(b []byte) (*EonSecretKey, error) { - epochSecretKey := new(EonSecretKey) - if err := epochSecretKey.Unmarshal(b); err != nil { - return nil, err - } - - return epochSecretKey, nil +func EpochSecretKeyFromBytes(b []byte) (*crypto.EpochSecretKey, error) { + epochSecretKey := new(crypto.EpochSecretKey) + err := epochSecretKey.Unmarshal(b) + return epochSecretKey, err } diff --git a/txnprovider/shutter/eon_tracker.go b/txnprovider/shutter/eon_tracker.go index 2629e8fe0ae..9d11cac1872 100644 --- a/txnprovider/shutter/eon_tracker.go +++ b/txnprovider/shutter/eon_tracker.go @@ -103,7 +103,7 @@ func (et *KsmEonTracker) RecentEon(index EonIndex) (Eon, bool) { et.mu.RLock() defer et.mu.RUnlock() - return et.recentEons.Get(Eon{Index: index}) + return et.recentEon(index) } func (et *KsmEonTracker) EonByBlockNum(blockNum uint64) (Eon, bool) { @@ -122,6 +122,10 @@ func (et *KsmEonTracker) EonByBlockNum(blockNum uint64) (Eon, bool) { return eon, found } +func (et *KsmEonTracker) recentEon(index EonIndex) (Eon, bool) { + return et.recentEons.Get(Eon{Index: index}) +} + func (et *KsmEonTracker) trackCurrentEon(ctx context.Context) error { blockEventC := make(chan BlockEvent) unregisterBlockEventObserver := et.blockListener.RegisterObserver(func(blockEvent BlockEvent) { @@ -149,15 +153,15 @@ func (et *KsmEonTracker) handleBlockEvent(blockEvent BlockEvent) error { et.mu.Lock() defer et.mu.Unlock() - eon, err := et.readEonAtNewBlockEvent(blockEvent.BlockNum) + eon, err := et.readEonAtNewBlockEvent(blockEvent.LatestBlockNum) if err != nil { return err } - et.logger.Debug("current eon at block", "blockNum", blockEvent.BlockNum, "eonIndex", eon.Index) + et.logger.Debug("current eon at block", "blockNum", blockEvent.LatestBlockNum, "eonIndex", eon.Index) et.currentEon = &eon et.recentEons.ReplaceOrInsert(eon) - et.maybeCleanup(blockEvent.BlockNum) + et.maybeCleanup(blockEvent.LatestBlockNum) return nil } @@ -178,7 +182,8 @@ func (et *KsmEonTracker) readEonAtNewBlockEvent(blockNum uint64) (Eon, error) { if err != nil { return Eon{}, err } - if eon, ok := et.RecentEon(EonIndex(eonIndex)); ok { + + if eon, ok := et.recentEon(EonIndex(eonIndex)); ok { cached = true return eon, nil } diff --git a/txnprovider/shutter/internal/testhelpers/eon_key_generation_mock_data.go b/txnprovider/shutter/internal/testhelpers/eon_key_generation_mock_data.go index c13f0303693..1e4618b0fb7 100644 --- a/txnprovider/shutter/internal/testhelpers/eon_key_generation_mock_data.go +++ b/txnprovider/shutter/internal/testhelpers/eon_key_generation_mock_data.go @@ -68,7 +68,7 @@ func (ekg EonKeyGeneration) DecryptionKeys(t *testing.T, signers []Keyper, ips s return keys } -func (ekg EonKeyGeneration) EpochSecretKey(t *testing.T, signers []Keyper, ip *shutter.IdentityPreimage) *shuttercrypto.EpochSecretKey { +func (ekg EonKeyGeneration) EpochSecretKey(t *testing.T, signers []Keyper, ip shutter.IdentityPreimage) *shuttercrypto.EpochSecretKey { epochSecretKeyShares := make([]*shuttercrypto.EpochSecretKeyShare, len(signers)) keyperIndices := make([]int, len(signers)) for i, keyper := range signers { @@ -96,7 +96,7 @@ func (k Keyper) Address() libcommon.Address { return crypto.PubkeyToAddress(k.PublicKey()) } -func (k Keyper) EpochSecretKeyShare(ip *shutter.IdentityPreimage) *shuttercrypto.EpochSecretKeyShare { +func (k Keyper) EpochSecretKeyShare(ip shutter.IdentityPreimage) *shuttercrypto.EpochSecretKeyShare { id := shuttercrypto.ComputeEpochID(ip[:]) return shuttercrypto.ComputeEpochSecretKeyShare(k.EonSecretKeyShare, id) } diff --git a/txnprovider/shutter/internal/testhelpers/identity_preimage_mock_data.go b/txnprovider/shutter/internal/testhelpers/identity_preimage_mock_data.go index 31d77c5b928..caa42f11df0 100644 --- a/txnprovider/shutter/internal/testhelpers/identity_preimage_mock_data.go +++ b/txnprovider/shutter/internal/testhelpers/identity_preimage_mock_data.go @@ -33,7 +33,7 @@ func MockIdentityPreimagesWithSlotIp(t *testing.T, slot uint64, count uint64) sh } func MockIdentityPreimages(t *testing.T, count uint64) shutter.IdentityPreimages { - ips := make([]*shutter.IdentityPreimage, count) + ips := make([]shutter.IdentityPreimage, count) for i := uint64(0); i < count; i++ { ips[i] = Uint64ToIdentityPreimage(t, i) } @@ -41,22 +41,22 @@ func MockIdentityPreimages(t *testing.T, count uint64) shutter.IdentityPreimages return ips } -func MakeSlotIdentityPreimage(t *testing.T, slot uint64) *shutter.IdentityPreimage { +func MakeSlotIdentityPreimage(t *testing.T, slot uint64) shutter.IdentityPreimage { // 32 bytes of zeros plus the block number as 20 byte big endian (ie starting with lots of // zeros as well). This ensures the block identity preimage is always alphanumerically before // any transaction identity preimages, because sender addresses cannot be that small. var buf bytes.Buffer buf.Write(libcommon.BigToHash(libcommon.Big0).Bytes()) buf.Write(libcommon.BigToHash(new(big.Int).SetUint64(slot)).Bytes()[12:]) - ip, err := shutter.IdentityPreimageFromSSZ(buf.Bytes()) + ip, err := shutter.IdentityPreimageFromBytes(buf.Bytes()) require.NoError(t, err) return ip } -func Uint64ToIdentityPreimage(t *testing.T, i uint64) *shutter.IdentityPreimage { +func Uint64ToIdentityPreimage(t *testing.T, i uint64) shutter.IdentityPreimage { buf := make([]byte, 52) binary.BigEndian.PutUint64(buf[:8], i) - ip, err := shutter.IdentityPreimageFromSSZ(buf) + ip, err := shutter.IdentityPreimageFromBytes(buf) require.NoError(t, err) return ip } diff --git a/txnprovider/shutter/pool.go b/txnprovider/shutter/pool.go index 3816c16a380..677deaef03e 100644 --- a/txnprovider/shutter/pool.go +++ b/txnprovider/shutter/pool.go @@ -38,6 +38,8 @@ type Pool struct { eonTracker EonTracker decryptionKeysListener DecryptionKeysListener decryptionKeysProcessor DecryptionKeysProcessor + encryptedTxnsPool *EncryptedTxnsPool + decryptedTxnsPool *DecryptedTxnsPool } func NewPool( @@ -53,7 +55,16 @@ func NewPool( eonTracker := NewKsmEonTracker(logger, config, blockListener, contractBackend) decryptionKeysValidator := NewDecryptionKeysExtendedValidator(logger, config, slotCalculator, eonTracker) decryptionKeysListener := NewDecryptionKeysListener(logger, config, decryptionKeysValidator) - decryptionKeysProcessor := NewDecryptionKeysProcessor(logger) + encryptedTxnsPool := NewEncryptedTxnsPool(logger, config, contractBackend, blockListener) + decryptedTxnsPool := NewDecryptedTxnsPool() + decryptionKeysProcessor := NewDecryptionKeysProcessor( + logger, + config, + encryptedTxnsPool, + decryptedTxnsPool, + blockListener, + slotCalculator, + ) return &Pool{ logger: logger, config: config, @@ -62,6 +73,8 @@ func NewPool( secondaryTxnProvider: secondaryTxnProvider, decryptionKeysListener: decryptionKeysListener, decryptionKeysProcessor: decryptionKeysProcessor, + encryptedTxnsPool: encryptedTxnsPool, + decryptedTxnsPool: decryptedTxnsPool, } } @@ -79,6 +92,7 @@ func (p Pool) Run(ctx context.Context) error { eg.Go(func() error { return p.eonTracker.Run(ctx) }) eg.Go(func() error { return p.decryptionKeysListener.Run(ctx) }) eg.Go(func() error { return p.decryptionKeysProcessor.Run(ctx) }) + eg.Go(func() error { return p.encryptedTxnsPool.Run(ctx) }) return eg.Wait() } From b59245bd4056055eafb2104ba93b66721507490c Mon Sep 17 00:00:00 2001 From: antonis19 Date: Wed, 19 Feb 2025 13:54:56 +0100 Subject: [PATCH 20/42] Add DB label to MDBX metrics (#13847) Currently the MDBX metrics recorded by `CollectMetrics()` are hardcoded , and work only for the main `chaindata` MDBX instance. In this PR I add a label to the MDBX metrics to denote the DB instance for which the metrics are collected. This will enable me to tune the performance of node DB : https://github.com/erigontech/erigon/issues/13550 --------- Co-authored-by: antonis19 --- erigon-lib/kv/kv_interface.go | 108 ++++++++++++++++++++++++--------- erigon-lib/kv/mdbx/kv_mdbx.go | 50 ++++++++------- erigon-lib/metrics/gaugevec.go | 60 ++++++++++++++++++ erigon-lib/metrics/register.go | 26 ++++++-- 4 files changed, 187 insertions(+), 57 deletions(-) create mode 100644 erigon-lib/metrics/gaugevec.go diff --git a/erigon-lib/kv/kv_interface.go b/erigon-lib/kv/kv_interface.go index da134bfde76..eef32ed7c92 100644 --- a/erigon-lib/kv/kv_interface.go +++ b/erigon-lib/kv/kv_interface.go @@ -76,34 +76,87 @@ import ( // 1. Application - rely on TemporalDB (Ex: ExecutionLayer) or just DB (Ex: TxPool, Sentry, Downloader). const ReadersLimit = 32000 // MDBX_READERS_LIMIT=32767 +const dbLabelName = "db" + +type DBGauges struct { // these gauges are shared by all MDBX instances, but need to be filtered by label + DbSize *metrics.GaugeVec + TxLimit *metrics.GaugeVec + TxSpill *metrics.GaugeVec + TxUnspill *metrics.GaugeVec + TxDirty *metrics.GaugeVec + TxRetired *metrics.GaugeVec + + DbPgopsNewly *metrics.GaugeVec + DbPgopsCow *metrics.GaugeVec + DbPgopsClone *metrics.GaugeVec + DbPgopsSplit *metrics.GaugeVec + DbPgopsMerge *metrics.GaugeVec + DbPgopsSpill *metrics.GaugeVec + DbPgopsUnspill *metrics.GaugeVec + DbPgopsWops *metrics.GaugeVec + + GcLeafMetric *metrics.GaugeVec + GcOverflowMetric *metrics.GaugeVec + GcPagesMetric *metrics.GaugeVec +} + +type DBSummaries struct { // the summaries are particular to a DB instance + DbCommitPreparation metrics.Summary + DbCommitWrite metrics.Summary + DbCommitSync metrics.Summary + DbCommitEnding metrics.Summary + DbCommitTotal metrics.Summary +} + +// this only needs to be called once during startup +func InitMDBXMGauges() *DBGauges { + return &DBGauges{ + DbSize: metrics.GetOrCreateGaugeVec(`db_size`, []string{dbLabelName}), + TxLimit: metrics.GetOrCreateGaugeVec(`tx_limit`, []string{dbLabelName}), + TxSpill: metrics.GetOrCreateGaugeVec(`tx_spill`, []string{dbLabelName}), + TxUnspill: metrics.GetOrCreateGaugeVec(`tx_unspill`, []string{dbLabelName}), + TxDirty: metrics.GetOrCreateGaugeVec(`tx_dirty`, []string{dbLabelName}), + TxRetired: metrics.GetOrCreateGaugeVec(`tx_retired`, []string{dbLabelName}), + DbPgopsNewly: metrics.GetOrCreateGaugeVec(`db_pgops{phase="newly"}`, []string{dbLabelName}), + DbPgopsCow: metrics.GetOrCreateGaugeVec(`db_pgops{phase="cow"}`, []string{dbLabelName}), + DbPgopsClone: metrics.GetOrCreateGaugeVec(`db_pgops{phase="clone"}`, []string{dbLabelName}), + DbPgopsSplit: metrics.GetOrCreateGaugeVec(`db_pgops{phase="split"}`, []string{dbLabelName}), + DbPgopsMerge: metrics.GetOrCreateGaugeVec(`db_pgops{phase="merge"}`, []string{dbLabelName}), + DbPgopsSpill: metrics.GetOrCreateGaugeVec(`db_pgops{phase="spill"}`, []string{dbLabelName}), + DbPgopsUnspill: metrics.GetOrCreateGaugeVec(`db_pgops{phase="unspill"}`, []string{dbLabelName}), + DbPgopsWops: metrics.GetOrCreateGaugeVec(`db_pgops{phase="wops"}`, []string{dbLabelName}), + + GcLeafMetric: metrics.GetOrCreateGaugeVec(`db_gc_leaf`, []string{dbLabelName}), + GcOverflowMetric: metrics.GetOrCreateGaugeVec(`db_gc_overflow`, []string{dbLabelName}), + GcPagesMetric: metrics.GetOrCreateGaugeVec(`db_gc_pages`, []string{dbLabelName}), + } +} + +// initialize summaries for a particular MDBX instance +func InitSummaries(dbLabel Label) { + // just in case the global singleton map is not already initialized + if MDBXSummaries == nil { + MDBXSummaries = make(map[Label]*DBSummaries) + } + + _, ok := MDBXSummaries[dbLabel] + if !ok { + dbName := string(dbLabel) + MDBXSummaries[dbLabel] = &DBSummaries{ + DbCommitPreparation: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "preparation"}), + DbCommitWrite: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "write"}), + DbCommitSync: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "sync"}), + DbCommitEnding: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "ending"}), + DbCommitTotal: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "total"}), + } + } +} + +var MDBXGauges *DBGauges = InitMDBXMGauges() // global mdbx gauges. each gauge can be filtered by db name +var MDBXSummaries map[Label]*DBSummaries = make(map[Label]*DBSummaries) // dbName => Summaries mapping var ( ErrAttemptToDeleteNonDeprecatedBucket = errors.New("only buckets from dbutils.ChaindataDeprecatedTables can be deleted") - - DbSize = metrics.GetOrCreateGauge(`db_size`) //nolint - TxLimit = metrics.GetOrCreateGauge(`tx_limit`) //nolint - TxSpill = metrics.GetOrCreateGauge(`tx_spill`) //nolint - TxUnspill = metrics.GetOrCreateGauge(`tx_unspill`) //nolint - TxDirty = metrics.GetOrCreateGauge(`tx_dirty`) //nolint - TxRetired = metrics.GetOrCreateGauge(`tx_retired`) //nolint - - DbCommitPreparation = metrics.GetOrCreateSummary(`db_commit_seconds{phase="preparation"}`) //nolint - //DbGCWallClock = metrics.GetOrCreateSummary(`db_commit_seconds{phase="gc_wall_clock"}`) //nolint - //DbGCCpuTime = metrics.GetOrCreateSummary(`db_commit_seconds{phase="gc_cpu_time"}`) //nolint - //DbCommitAudit = metrics.GetOrCreateSummary(`db_commit_seconds{phase="audit"}`) //nolint - DbCommitWrite = metrics.GetOrCreateSummary(`db_commit_seconds{phase="write"}`) //nolint - DbCommitSync = metrics.GetOrCreateSummary(`db_commit_seconds{phase="sync"}`) //nolint - DbCommitEnding = metrics.GetOrCreateSummary(`db_commit_seconds{phase="ending"}`) //nolint - DbCommitTotal = metrics.GetOrCreateSummary(`db_commit_seconds{phase="total"}`) //nolint - - DbPgopsNewly = metrics.GetOrCreateGauge(`db_pgops{phase="newly"}`) //nolint - DbPgopsCow = metrics.GetOrCreateGauge(`db_pgops{phase="cow"}`) //nolint - DbPgopsClone = metrics.GetOrCreateGauge(`db_pgops{phase="clone"}`) //nolint - DbPgopsSplit = metrics.GetOrCreateGauge(`db_pgops{phase="split"}`) //nolint - DbPgopsMerge = metrics.GetOrCreateGauge(`db_pgops{phase="merge"}`) //nolint - DbPgopsSpill = metrics.GetOrCreateGauge(`db_pgops{phase="spill"}`) //nolint - DbPgopsUnspill = metrics.GetOrCreateGauge(`db_pgops{phase="unspill"}`) //nolint - DbPgopsWops = metrics.GetOrCreateGauge(`db_pgops{phase="wops"}`) //nolint /* DbPgopsPrefault = metrics.NewCounter(`db_pgops{phase="prefault"}`) //nolint DbPgopsMinicore = metrics.NewCounter(`db_pgops{phase="minicore"}`) //nolint @@ -136,11 +189,6 @@ var ( //DbGcSelfPnlMergeTime = metrics.GetOrCreateSummary(`db_gc_pnl_seconds{phase="slef_merge_time"}`) //nolint //DbGcSelfPnlMergeVolume = metrics.NewCounter(`db_gc_pnl{phase="self_merge_volume"}`) //nolint //DbGcSelfPnlMergeCalls = metrics.NewCounter(`db_gc_pnl{phase="slef_merge_calls"}`) //nolint - - GcLeafMetric = metrics.GetOrCreateGauge(`db_gc_leaf`) //nolint - GcOverflowMetric = metrics.GetOrCreateGauge(`db_gc_overflow`) //nolint - GcPagesMetric = metrics.GetOrCreateGauge(`db_gc_pages`) //nolint - ) type DBVerbosityLvl int8 diff --git a/erigon-lib/kv/mdbx/kv_mdbx.go b/erigon-lib/kv/mdbx/kv_mdbx.go index c0009292ef9..f3f7007da39 100644 --- a/erigon-lib/kv/mdbx/kv_mdbx.go +++ b/erigon-lib/kv/mdbx/kv_mdbx.go @@ -101,6 +101,9 @@ func New(label kv.Label, log log.Logger) MdbxOpts { if label == kv.ChainDB { opts = opts.RemoveFlags(mdbx.NoReadahead) // enable readahead for chaindata by default. Erigon3 require fast updates and prune. Also it's chaindata is small (doesen GB) } + if opts.metrics { + kv.InitSummaries(label) + } return opts } @@ -699,34 +702,35 @@ func (tx *MdbxTx) CollectMetrics() { } } - kv.DbSize.SetUint64(info.Geo.Current) - kv.DbPgopsNewly.SetUint64(info.PageOps.Newly) - kv.DbPgopsCow.SetUint64(info.PageOps.Cow) - kv.DbPgopsClone.SetUint64(info.PageOps.Clone) - kv.DbPgopsSplit.SetUint64(info.PageOps.Split) - kv.DbPgopsMerge.SetUint64(info.PageOps.Merge) - kv.DbPgopsSpill.SetUint64(info.PageOps.Spill) - kv.DbPgopsUnspill.SetUint64(info.PageOps.Unspill) - kv.DbPgopsWops.SetUint64(info.PageOps.Wops) + var dbLabel = string(tx.db.opts.label) + kv.MDBXGauges.DbSize.WithLabelValues(dbLabel).SetUint64(info.Geo.Current) + kv.MDBXGauges.DbPgopsNewly.WithLabelValues(dbLabel).SetUint64(info.PageOps.Newly) + kv.MDBXGauges.DbPgopsCow.WithLabelValues(dbLabel).SetUint64(info.PageOps.Cow) + kv.MDBXGauges.DbPgopsClone.WithLabelValues(dbLabel).SetUint64(info.PageOps.Clone) + kv.MDBXGauges.DbPgopsSplit.WithLabelValues(dbLabel).SetUint64(info.PageOps.Split) + kv.MDBXGauges.DbPgopsMerge.WithLabelValues(dbLabel).SetUint64(info.PageOps.Merge) + kv.MDBXGauges.DbPgopsSpill.WithLabelValues(dbLabel).SetUint64(info.PageOps.Spill) + kv.MDBXGauges.DbPgopsUnspill.WithLabelValues(dbLabel).SetUint64(info.PageOps.Unspill) + kv.MDBXGauges.DbPgopsWops.WithLabelValues(dbLabel).SetUint64(info.PageOps.Wops) txInfo, err := tx.tx.Info(true) if err != nil { return } - kv.TxDirty.SetUint64(txInfo.SpaceDirty) - kv.TxRetired.SetUint64(txInfo.SpaceRetired) - kv.TxLimit.SetUint64(tx.db.txSize) - kv.TxSpill.SetUint64(txInfo.Spill) - kv.TxUnspill.SetUint64(txInfo.Unspill) + kv.MDBXGauges.TxDirty.WithLabelValues(dbLabel).SetUint64(txInfo.SpaceDirty) + kv.MDBXGauges.TxRetired.WithLabelValues(dbLabel).SetUint64(txInfo.SpaceRetired) + kv.MDBXGauges.TxLimit.WithLabelValues(dbLabel).SetUint64(tx.db.txSize) + kv.MDBXGauges.TxSpill.WithLabelValues(dbLabel).SetUint64(txInfo.Spill) + kv.MDBXGauges.TxUnspill.WithLabelValues(dbLabel).SetUint64(txInfo.Unspill) gc, err := tx.BucketStat("gc") if err != nil { return } - kv.GcLeafMetric.SetUint64(gc.LeafPages) - kv.GcOverflowMetric.SetUint64(gc.OverflowPages) - kv.GcPagesMetric.SetUint64((gc.LeafPages + gc.OverflowPages) * tx.db.opts.pageSize.Bytes() / 8) + kv.MDBXGauges.GcLeafMetric.WithLabelValues(dbLabel).SetUint64(gc.LeafPages) + kv.MDBXGauges.GcOverflowMetric.WithLabelValues(dbLabel).SetUint64(gc.OverflowPages) + kv.MDBXGauges.GcPagesMetric.WithLabelValues(dbLabel).SetUint64((gc.LeafPages + gc.OverflowPages) * tx.db.opts.pageSize.Bytes() / 8) } func (tx *MdbxTx) WarmupDB(force bool) error { @@ -908,12 +912,12 @@ func (tx *MdbxTx) Commit() error { } if tx.db.opts.metrics { - kv.DbCommitPreparation.Observe(latency.Preparation.Seconds()) - //kv.DbCommitAudit.Update(latency.Audit.Seconds()) - kv.DbCommitWrite.Observe(latency.Write.Seconds()) - kv.DbCommitSync.Observe(latency.Sync.Seconds()) - kv.DbCommitEnding.Observe(latency.Ending.Seconds()) - kv.DbCommitTotal.Observe(latency.Whole.Seconds()) + dbLabel := tx.db.opts.label + kv.MDBXSummaries[dbLabel].DbCommitPreparation.Observe(latency.Preparation.Seconds()) + kv.MDBXSummaries[dbLabel].DbCommitWrite.Observe(latency.Write.Seconds()) + kv.MDBXSummaries[dbLabel].DbCommitSync.Observe(latency.Sync.Seconds()) + kv.MDBXSummaries[dbLabel].DbCommitEnding.Observe(latency.Ending.Seconds()) + kv.MDBXSummaries[dbLabel].DbCommitTotal.Observe(latency.Whole.Seconds()) //kv.DbGcWorkPnlMergeTime.Update(latency.GCDetails.WorkPnlMergeTime.Seconds()) //kv.DbGcWorkPnlMergeVolume.Set(uint64(latency.GCDetails.WorkPnlMergeVolume)) diff --git a/erigon-lib/metrics/gaugevec.go b/erigon-lib/metrics/gaugevec.go new file mode 100644 index 00000000000..d422c1583cf --- /dev/null +++ b/erigon-lib/metrics/gaugevec.go @@ -0,0 +1,60 @@ +package metrics + +import "github.com/prometheus/client_golang/prometheus" + +// Since we wrapped prometheus.Gauge into a custom interface, we need to wrap prometheus.GaugeVec into a custom struct +type GaugeVec struct { + *prometheus.GaugeVec +} + +func (gv *GaugeVec) Collect(ch chan<- prometheus.Metric) { + gv.MetricVec.Collect(ch) +} + +func (gv *GaugeVec) CurryWith(labels prometheus.Labels) (*GaugeVec, error) { + gv2, err := gv.GaugeVec.CurryWith(labels) + return &GaugeVec{gv2}, err + +} + +func (gv *GaugeVec) Delete(labels prometheus.Labels) bool { + return gv.GaugeVec.MetricVec.Delete(labels) +} + +func (gv *GaugeVec) DeleteLabelValues(lvs ...string) bool { + return gv.GaugeVec.MetricVec.DeleteLabelValues(lvs...) +} + +func (gv *GaugeVec) DeletePartialMatch(labels prometheus.Labels) int { + return gv.GaugeVec.MetricVec.DeletePartialMatch(labels) +} + +func (gv *GaugeVec) Describe(ch chan<- *prometheus.Desc) { + gv.GaugeVec.MetricVec.Describe(ch) +} + +func (gv *GaugeVec) GetMetricWith(labels prometheus.Labels) (Gauge, error) { + g, err := gv.GaugeVec.GetMetricWith(labels) + return &gauge{g}, err +} + +func (gv *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) { + g, err := gv.GaugeVec.GetMetricWithLabelValues(lvs...) + return &gauge{g}, err +} + +func (gv *GaugeVec) MustCurryWith(labels prometheus.Labels) *GaugeVec { + return &GaugeVec{gv.GaugeVec.MustCurryWith(labels)} +} + +func (gv *GaugeVec) Reset() { + gv.GaugeVec.MetricVec.Reset() +} + +func (gv *GaugeVec) With(labels prometheus.Labels) Gauge { + return &gauge{gv.GaugeVec.With(labels)} +} + +func (gv *GaugeVec) WithLabelValues(lvs ...string) Gauge { + return &gauge{gv.GaugeVec.WithLabelValues(lvs...)} +} diff --git a/erigon-lib/metrics/register.go b/erigon-lib/metrics/register.go index cbd07501271..28d9dfb6865 100644 --- a/erigon-lib/metrics/register.go +++ b/erigon-lib/metrics/register.go @@ -18,8 +18,6 @@ package metrics import ( "fmt" - - "github.com/prometheus/client_golang/prometheus" ) // NewCounter registers and returns new counter with the given name. @@ -117,13 +115,13 @@ func GetOrCreateGauge(name string) Gauge { // - foo, with labels []string{"bar", "baz"} // // The returned GaugeVec is safe to use from concurrent goroutines. -func GetOrCreateGaugeVec(name string, labels []string, help ...string) *prometheus.GaugeVec { +func GetOrCreateGaugeVec(name string, labels []string, help ...string) *GaugeVec { gv, err := defaultSet.GetOrCreateGaugeVec(name, labels, help...) if err != nil { panic(fmt.Errorf("could not get or create new gaugevec: %w", err)) } - return gv + return &GaugeVec{gv} } // NewSummary creates and returns new summary with the given name. @@ -168,6 +166,26 @@ func GetOrCreateSummary(name string) Summary { return &summary{s} } +// add labels to metric name +func buildLabeledName(baseName string, labelNames, labelValues []string) string { + if len(labelNames) == 0 { + return baseName + } + baseName += "{" + baseName += fmt.Sprintf(`%s="%s"`, labelNames[0], labelValues[0]) + + for i := 1; i < len(labelNames); i++ { + baseName += fmt.Sprintf(`,%s="%s"`, labelNames[i], labelValues[i]) + } + baseName += "}" + return baseName +} + +func GetOrCreateSummaryWithLabels(name string, labelNames, labelValues []string) Summary { + labeledName := buildLabeledName(name, labelNames, labelValues) + return GetOrCreateSummary(labeledName) +} + // NewHistogram creates and returns new histogram with the given name. // // name must be valid Prometheus-compatible metric with possible labels. From 54548aca1940750e444f141840404479cfa03638 Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:22:56 +0000 Subject: [PATCH 21/42] shutter: implement provide txns (#13865) closes https://github.com/erigontech/erigon/issues/13385 --- eth/stagedsync/stage_mining_exec.go | 1 + txnprovider/provider.go | 7 ++ txnprovider/shutter/block_tracker.go | 109 ++++++++++++++++++ txnprovider/shutter/config.go | 5 + .../shutter/decryption_keys_processor.go | 2 + .../testhelpers/slot_calculator_mock.go | 38 ++++++ txnprovider/shutter/pool.go | 100 ++++++++++++++-- txnprovider/shutter/slot_calculator.go | 5 + 8 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 txnprovider/shutter/block_tracker.go diff --git a/eth/stagedsync/stage_mining_exec.go b/eth/stagedsync/stage_mining_exec.go index 682111c2d3b..4ad1f06f38b 100644 --- a/eth/stagedsync/stage_mining_exec.go +++ b/eth/stagedsync/stage_mining_exec.go @@ -269,6 +269,7 @@ func getNextTransactions( provideOpts := []txnprovider.ProvideOption{ txnprovider.WithAmount(amount), txnprovider.WithParentBlockNum(executionAt), + txnprovider.WithBlockTime(header.Time), txnprovider.WithGasTarget(remainingGas), txnprovider.WithBlobGasTarget(remainingBlobGas), txnprovider.WithTxnIdsFilter(alreadyYielded), diff --git a/txnprovider/provider.go b/txnprovider/provider.go index 036239e9ac3..4a2e0639ac9 100644 --- a/txnprovider/provider.go +++ b/txnprovider/provider.go @@ -43,6 +43,12 @@ func WithParentBlockNum(blockNum uint64) ProvideOption { } } +func WithBlockTime(blockTime uint64) ProvideOption { + return func(opt *ProvideOptions) { + opt.BlockTime = blockTime + } +} + func WithAmount(amount int) ProvideOption { return func(opt *ProvideOptions) { opt.Amount = amount @@ -68,6 +74,7 @@ func WithTxnIdsFilter(txnIdsFilter mapset.Set[[32]byte]) ProvideOption { } type ProvideOptions struct { + BlockTime uint64 ParentBlockNum uint64 Amount int GasTarget uint64 diff --git a/txnprovider/shutter/block_tracker.go b/txnprovider/shutter/block_tracker.go new file mode 100644 index 00000000000..5f1d7b7e387 --- /dev/null +++ b/txnprovider/shutter/block_tracker.go @@ -0,0 +1,109 @@ +// Copyright 2025 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 shutter + +import ( + "context" + "errors" + "sync" + + "github.com/erigontech/erigon-lib/log/v3" +) + +type BlockTracker struct { + logger log.Logger + blockListener BlockListener + blockChangeMu *sync.Mutex + blockChangeCond *sync.Cond + currentBlockNum uint64 + stopped bool +} + +func NewBlockTracker(logger log.Logger, blockListener BlockListener) BlockTracker { + blockChangeMu := sync.Mutex{} + return BlockTracker{ + logger: logger, + blockListener: blockListener, + blockChangeMu: &blockChangeMu, + blockChangeCond: sync.NewCond(&blockChangeMu), + } +} + +func (bt BlockTracker) Run(ctx context.Context) error { + defer bt.logger.Info("block tracker stopped") + bt.logger.Info("running block tracker") + + defer func() { + // make sure we wake up all waiters upon getting stopped + bt.blockChangeMu.Lock() + bt.stopped = true + bt.blockChangeCond.Broadcast() + bt.blockChangeMu.Unlock() + }() + + blockEventC := make(chan BlockEvent) + unregisterBlockEventObserver := bt.blockListener.RegisterObserver(func(blockEvent BlockEvent) { + select { + case <-ctx.Done(): // no-op + case blockEventC <- blockEvent: + } + }) + + defer unregisterBlockEventObserver() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case blockEvent := <-blockEventC: + bt.blockChangeMu.Lock() + bt.currentBlockNum = blockEvent.LatestBlockNum + bt.blockChangeCond.Broadcast() + bt.blockChangeMu.Unlock() + } + } +} + +func (bt BlockTracker) Wait(ctx context.Context, blockNum uint64) error { + done := make(chan struct{}) + go func() { + defer close(done) + + bt.blockChangeMu.Lock() + defer bt.blockChangeMu.Unlock() + + for bt.currentBlockNum < blockNum && !bt.stopped && ctx.Err() == nil { + bt.blockChangeCond.Wait() + } + }() + + select { + case <-ctx.Done(): + // note the below will wake up all waiters prematurely, but thanks to the for loop condition + // in the waiting goroutine the ones that still need to wait will go back to sleep + bt.blockChangeCond.Broadcast() + return ctx.Err() + case <-done: + bt.blockChangeMu.Lock() + defer bt.blockChangeMu.Unlock() + + if bt.currentBlockNum < blockNum && bt.stopped { + return errors.New("block tracker stopped") + } + + return nil + } +} diff --git a/txnprovider/shutter/config.go b/txnprovider/shutter/config.go index cfe75ff1897..10eb1de9ee1 100644 --- a/txnprovider/shutter/config.go +++ b/txnprovider/shutter/config.go @@ -18,6 +18,7 @@ package shutter import ( "crypto/ecdsa" + "time" "github.com/holiman/uint256" "github.com/libp2p/go-libp2p/core/peer" @@ -44,6 +45,7 @@ type Config struct { MaxPooledEncryptedTxns int EncryptedGasLimit uint64 EncryptedTxnsLookBackDistance uint64 + MaxDecryptionKeysDelay time.Duration } type P2pConfig struct { @@ -98,6 +100,7 @@ var ( MaxPooledEncryptedTxns: defaultMaxPooledEncryptedTxns, EncryptedGasLimit: defaultEncryptedGasLimit, EncryptedTxnsLookBackDistance: defaultEncryptedTxnsLookBackDistance, + MaxDecryptionKeysDelay: defaultMaxDecryptionKeysDelay, P2pConfig: P2pConfig{ ListenPort: defaultP2PListenPort, BootstrapNodes: []string{ @@ -122,6 +125,7 @@ var ( MaxPooledEncryptedTxns: defaultMaxPooledEncryptedTxns, EncryptedGasLimit: defaultEncryptedGasLimit, EncryptedTxnsLookBackDistance: defaultEncryptedTxnsLookBackDistance, + MaxDecryptionKeysDelay: defaultMaxDecryptionKeysDelay, P2pConfig: P2pConfig{ ListenPort: defaultP2PListenPort, BootstrapNodes: []string{ @@ -139,4 +143,5 @@ const ( defaultMaxPooledEncryptedTxns = 10_000 defaultEncryptedGasLimit = 10_000_000 defaultEncryptedTxnsLookBackDistance = 128 + defaultMaxDecryptionKeysDelay = 1_666 * time.Millisecond ) diff --git a/txnprovider/shutter/decryption_keys_processor.go b/txnprovider/shutter/decryption_keys_processor.go index 9ad3c3be254..10835b52493 100644 --- a/txnprovider/shutter/decryption_keys_processor.go +++ b/txnprovider/shutter/decryption_keys_processor.go @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with Erigon. If not, see . +//go:build !abigen + package shutter import ( diff --git a/txnprovider/shutter/internal/testhelpers/slot_calculator_mock.go b/txnprovider/shutter/internal/testhelpers/slot_calculator_mock.go index 8bdee08a2a2..954246cddfe 100644 --- a/txnprovider/shutter/internal/testhelpers/slot_calculator_mock.go +++ b/txnprovider/shutter/internal/testhelpers/slot_calculator_mock.go @@ -154,3 +154,41 @@ func (c *MockSlotCalculatorCalcSlotAgeCall) DoAndReturn(f func(uint64) time.Dura c.Call = c.Call.DoAndReturn(f) return c } + +// SecondsPerSlot mocks base method. +func (m *MockSlotCalculator) SecondsPerSlot() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SecondsPerSlot") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// SecondsPerSlot indicates an expected call of SecondsPerSlot. +func (mr *MockSlotCalculatorMockRecorder) SecondsPerSlot() *MockSlotCalculatorSecondsPerSlotCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SecondsPerSlot", reflect.TypeOf((*MockSlotCalculator)(nil).SecondsPerSlot)) + return &MockSlotCalculatorSecondsPerSlotCall{Call: call} +} + +// MockSlotCalculatorSecondsPerSlotCall wrap *gomock.Call +type MockSlotCalculatorSecondsPerSlotCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockSlotCalculatorSecondsPerSlotCall) Return(arg0 uint64) *MockSlotCalculatorSecondsPerSlotCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockSlotCalculatorSecondsPerSlotCall) Do(f func() uint64) *MockSlotCalculatorSecondsPerSlotCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockSlotCalculatorSecondsPerSlotCall) DoAndReturn(f func() uint64) *MockSlotCalculatorSecondsPerSlotCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/txnprovider/shutter/pool.go b/txnprovider/shutter/pool.go index 677deaef03e..65c867d06a9 100644 --- a/txnprovider/shutter/pool.go +++ b/txnprovider/shutter/pool.go @@ -14,10 +14,15 @@ // You should have received a copy of the GNU Lesser General Public License // along with Erigon. If not, see . +//go:build !abigen + package shutter import ( "context" + "errors" + "fmt" + "time" "golang.org/x/sync/errgroup" @@ -35,11 +40,13 @@ type Pool struct { config Config secondaryTxnProvider txnprovider.TxnProvider blockListener BlockListener + blockTracker BlockTracker eonTracker EonTracker decryptionKeysListener DecryptionKeysListener decryptionKeysProcessor DecryptionKeysProcessor encryptedTxnsPool *EncryptedTxnsPool decryptedTxnsPool *DecryptedTxnsPool + slotCalculator SlotCalculator } func NewPool( @@ -52,6 +59,7 @@ func NewPool( logger = logger.New("component", "shutter") slotCalculator := NewBeaconChainSlotCalculator(config.BeaconChainGenesisTimestamp, config.SecondsPerSlot) blockListener := NewBlockListener(logger, stateChangesClient) + blockTracker := NewBlockTracker(logger, blockListener) eonTracker := NewKsmEonTracker(logger, config, blockListener, contractBackend) decryptionKeysValidator := NewDecryptionKeysExtendedValidator(logger, config, slotCalculator, eonTracker) decryptionKeysListener := NewDecryptionKeysListener(logger, config, decryptionKeysValidator) @@ -69,12 +77,14 @@ func NewPool( logger: logger, config: config, blockListener: blockListener, + blockTracker: blockTracker, eonTracker: eonTracker, secondaryTxnProvider: secondaryTxnProvider, decryptionKeysListener: decryptionKeysListener, decryptionKeysProcessor: decryptionKeysProcessor, encryptedTxnsPool: encryptedTxnsPool, decryptedTxnsPool: decryptedTxnsPool, + slotCalculator: slotCalculator, } } @@ -89,6 +99,7 @@ func (p Pool) Run(ctx context.Context) error { eg, ctx := errgroup.WithContext(ctx) eg.Go(func() error { return p.blockListener.Run(ctx) }) + eg.Go(func() error { return p.blockTracker.Run(ctx) }) eg.Go(func() error { return p.eonTracker.Run(ctx) }) eg.Go(func() error { return p.decryptionKeysListener.Run(ctx) }) eg.Go(func() error { return p.decryptionKeysProcessor.Run(ctx) }) @@ -97,11 +108,86 @@ func (p Pool) Run(ctx context.Context) error { } func (p Pool) ProvideTxns(ctx context.Context, opts ...txnprovider.ProvideOption) ([]types.Transaction, error) { - // - // TODO - implement shutter spec - // 1) fetch corresponding txns for current slot and fill the remaining gas - // with the secondary txn provider (devp2p) - // 2) if no decryption keys arrive for current slot then return empty transactions - // - return p.secondaryTxnProvider.ProvideTxns(ctx, opts...) + provideOpts := txnprovider.ApplyProvideOptions(opts...) + blockTime := provideOpts.BlockTime + if blockTime == 0 { + return nil, errors.New("block time option is required by the shutter provider") + } + + parentBlockNum := provideOpts.ParentBlockNum + parentBlockWaitTime := time.Second * time.Duration(p.slotCalculator.SecondsPerSlot()) + parentBlockWaitCtx, parentBlockWaitCtxCancel := context.WithTimeout(ctx, parentBlockWaitTime) + defer parentBlockWaitCtxCancel() + err := p.blockTracker.Wait(parentBlockWaitCtx, parentBlockNum) + if err != nil { + return nil, fmt.Errorf("issue while waiting for parent block %d: %w", parentBlockNum, err) + } + + eon, ok := p.eonTracker.EonByBlockNum(parentBlockNum) + if !ok { + return nil, fmt.Errorf("unknown eon for block num %d", parentBlockNum) + } + + slot, err := p.slotCalculator.CalcSlot(blockTime) + if err != nil { + return nil, err + } + + decryptionMark := DecryptionMark{Slot: slot, Eon: eon.Index} + slotAge := p.slotCalculator.CalcSlotAge(slot) + keysWaitTime := p.config.MaxDecryptionKeysDelay - slotAge + decryptionWaitCtx, decryptionWaitCtxCancel := context.WithTimeout(ctx, keysWaitTime) + defer decryptionWaitCtxCancel() + err = p.decryptedTxnsPool.Wait(decryptionWaitCtx, decryptionMark) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + p.logger.Warn( + "decryption keys wait timeout, falling back to secondary txn provider", + "slot", slot, + "age", slotAge, + ) + + // Note: specs say to produce empty block in case decryption keys do not arrive on time. + // However, upon discussion with Shutter and Nethermind it was agreed that this is not + // practical at this point in time as it can hurt validator rewards across the network, + // and also it doesn't in any way prevent any cheating from happening. + // To properly address cheating, we need a mechanism for slashing which is a future + // work stream item for the Shutter team. For now, we follow what Nethermind does + // and fallback to the public devp2p mempool - any changes to this should be + // co-ordinated with them. + return p.secondaryTxnProvider.ProvideTxns(ctx, opts...) + } + + return nil, err + } + + return p.provide(ctx, decryptionMark, opts...) +} + +func (p Pool) provide(ctx context.Context, mark DecryptionMark, opts ...txnprovider.ProvideOption) ([]types.Transaction, error) { + decryptedTxns, ok := p.decryptedTxnsPool.DecryptedTxns(mark) + if !ok { + return nil, fmt.Errorf("unexpected missing decrypted txns for mark: slot=%d, eon=%d", mark.Slot, mark.Eon) + } + + decryptedTxnsGas := decryptedTxns.TotalGasLimit + provideOpts := txnprovider.ApplyProvideOptions(opts...) + totalGasTarget := provideOpts.GasTarget + if decryptedTxnsGas > totalGasTarget { + // note this should never happen because EncryptedGasLimit must always be <= gasLimit for a block + return nil, fmt.Errorf("decrypted txns gas gt target: %d > %d", decryptedTxnsGas, totalGasTarget) + } + + if decryptedTxnsGas == totalGasTarget { + return decryptedTxns.Transactions, nil + } + + remGasTarget := totalGasTarget - decryptedTxnsGas + opts = append(opts, txnprovider.WithGasTarget(remGasTarget)) // overrides option + additionalTxns, err := p.secondaryTxnProvider.ProvideTxns(ctx, opts...) + if err != nil { + return nil, err + } + + return append(decryptedTxns.Transactions, additionalTxns...), nil } diff --git a/txnprovider/shutter/slot_calculator.go b/txnprovider/shutter/slot_calculator.go index ebcebaaa40d..45abe22ceea 100644 --- a/txnprovider/shutter/slot_calculator.go +++ b/txnprovider/shutter/slot_calculator.go @@ -28,6 +28,7 @@ type SlotCalculator interface { CalcSlot(timestamp uint64) (uint64, error) CalcSlotAge(slot uint64) time.Duration CalcCurrentSlot() uint64 + SecondsPerSlot() uint64 } type BeaconChainSlotCalculator struct { @@ -63,3 +64,7 @@ func (sc BeaconChainSlotCalculator) CalcCurrentSlot() uint64 { return slot } + +func (sc BeaconChainSlotCalculator) SecondsPerSlot() uint64 { + return sc.secondsPerSlot +} From 57115c92459bf1c7895d05fad5b4deb69c6ee2ce Mon Sep 17 00:00:00 2001 From: Ostroukhov Nikita Date: Wed, 19 Feb 2025 16:00:36 +0000 Subject: [PATCH 22/42] =?UTF-8?q?Removed=20excessive=20check=20inside=20un?= =?UTF-8?q?it=20tests=20that=20caused=20the=20CI=20to=20time=20=E2=80=A6?= =?UTF-8?q?=20(#13863)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …out sometimes --- polygon/heimdall/service_test.go | 84 +++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/polygon/heimdall/service_test.go b/polygon/heimdall/service_test.go index d8a1367819e..0485e4f6c54 100644 --- a/polygon/heimdall/service_test.go +++ b/polygon/heimdall/service_test.go @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with Erigon. If not, see . +//go:build integration + package heimdall import ( @@ -45,13 +47,14 @@ import ( func TestServiceWithAmoyData(t *testing.T) { suite.Run(t, &ServiceTestSuite{ - testDataDir: "testdata/amoy", - chainConfig: params.AmoyChainConfig, - expectedLastSpan: 1280, - expectedFirstCheckpoint: 1, - expectedLastCheckpoint: 150, - expectedFirstMilestone: 285542, - expectedLastMilestone: 285641, + testDataDir: "testdata/amoy", + chainConfig: params.AmoyChainConfig, + expectedLastSpan: 1280, + expectedFirstCheckpoint: 1, + expectedLastCheckpoint: 150, + expectedFirstMilestone: 285542, + expectedLastMilestone: 285641, + entityNumberToCheckBeforeFirst: 2, producersApiBlocksToTest: []uint64{ // span 0 1, // start @@ -82,13 +85,14 @@ func TestServiceWithAmoyData(t *testing.T) { func TestServiceWithMainnetData(t *testing.T) { suite.Run(t, &ServiceTestSuite{ - testDataDir: "testdata/mainnet", - chainConfig: params.BorMainnetChainConfig, - expectedLastSpan: 2344, - expectedFirstCheckpoint: 1, - expectedLastCheckpoint: 1, - expectedFirstMilestone: 453496, - expectedLastMilestone: 453496, + testDataDir: "testdata/mainnet", + chainConfig: params.BorMainnetChainConfig, + expectedLastSpan: 2344, + expectedFirstCheckpoint: 1, + expectedLastCheckpoint: 1, + expectedFirstMilestone: 453496, + expectedLastMilestone: 453496, + entityNumberToCheckBeforeFirst: 2, producersApiBlocksToTest: []uint64{ 1, 16, @@ -121,19 +125,21 @@ func TestServiceWithMainnetData(t *testing.T) { type ServiceTestSuite struct { // test suite inputs - testDataDir string - chainConfig *chain.Config - expectedFirstSpan uint64 - expectedLastSpan uint64 - expectedFirstCheckpoint uint64 - expectedLastCheckpoint uint64 - expectedFirstMilestone uint64 - expectedLastMilestone uint64 - producersApiBlocksToTest []uint64 + testDataDir string + chainConfig *chain.Config + expectedFirstSpan uint64 + expectedLastSpan uint64 + expectedFirstCheckpoint uint64 + expectedLastCheckpoint uint64 + expectedFirstMilestone uint64 + expectedLastMilestone uint64 + entityNumberToCheckBeforeFirst uint64 + producersApiBlocksToTest []uint64 // test suite internals suite.Suite ctx context.Context + logger log.Logger cancel context.CancelFunc eg errgroup.Group client *MockClient @@ -151,8 +157,8 @@ func (suite *ServiceTestSuite) SetupSuite() { ctrl := gomock.NewController(suite.T()) tempDir := suite.T().TempDir() dataDir := fmt.Sprintf("%s/datadir", tempDir) - logger := testlog.Logger(suite.T(), log.LvlCrit) - store := NewMdbxStore(logger, dataDir, false, 1) + suite.logger = testlog.Logger(suite.T(), log.LvlCrit) + store := NewMdbxStore(suite.logger, dataDir, false, 1) borConfig := suite.chainConfig.Bor.(*borcfg.BorConfig) suite.ctx, suite.cancel = context.WithCancel(context.Background()) suite.spansTestDataDir = filepath.Join(suite.testDataDir, "spans") @@ -167,7 +173,7 @@ func (suite *ServiceTestSuite) SetupSuite() { Store: store, BorConfig: borConfig, Client: suite.client, - Logger: logger, + Logger: suite.logger, }) err := suite.service.store.Prepare(suite.ctx) @@ -200,8 +206,10 @@ func (suite *ServiceTestSuite) SetupSuite() { } func (suite *ServiceTestSuite) TearDownSuite() { + suite.logger.Info("tear down test case") suite.cancel() err := suite.eg.Wait() + suite.logger.Info("test has been torn down") require.ErrorIs(suite.T(), err, context.Canceled) } @@ -215,7 +223,13 @@ func (suite *ServiceTestSuite) TestMilestones() { require.True(t, ok) require.Equal(t, suite.expectedLastMilestone, id) - for id := uint64(0); id < suite.expectedFirstMilestone; id++ { + checkFrom := uint64(0) + + if suite.expectedFirstMilestone > suite.entityNumberToCheckBeforeFirst { + checkFrom = suite.expectedFirstMilestone - suite.entityNumberToCheckBeforeFirst + } + + for id := checkFrom; id < suite.expectedFirstMilestone; id++ { entity, ok, err := svc.store.Milestones().Entity(ctx, id) require.NoError(t, err) require.False(t, ok) @@ -244,7 +258,13 @@ func (suite *ServiceTestSuite) TestCheckpoints() { require.True(t, ok) require.Equal(t, suite.expectedLastCheckpoint, id) - for id := uint64(0); id < suite.expectedFirstCheckpoint; id++ { + checkFrom := uint64(0) + + if suite.expectedFirstCheckpoint > suite.entityNumberToCheckBeforeFirst { + checkFrom = suite.expectedFirstCheckpoint - suite.entityNumberToCheckBeforeFirst + } + + for id := checkFrom; id < suite.expectedFirstCheckpoint; id++ { entity, ok, err := svc.store.Checkpoints().Entity(ctx, id) require.NoError(t, err) require.False(t, ok) @@ -269,7 +289,13 @@ func (suite *ServiceTestSuite) TestSpans() { require.True(t, ok) require.Equal(t, suite.expectedLastSpan, id) - for id := uint64(0); id < suite.expectedFirstSpan; id++ { + checkFrom := uint64(0) + + if suite.expectedFirstSpan > suite.entityNumberToCheckBeforeFirst { + checkFrom = suite.expectedFirstSpan - suite.entityNumberToCheckBeforeFirst + } + + for id := checkFrom; id < suite.expectedFirstSpan; id++ { entity, ok, err := svc.store.Spans().Entity(ctx, id) require.NoError(t, err) require.False(t, ok) From a261f9eaae84945b6dabfd34bdc0d39cce978d8a Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Thu, 20 Feb 2025 02:22:23 +0100 Subject: [PATCH 23/42] up dep: bls (#13875) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 96d126ca531..830194d7cba 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( require ( gfx.cafe/util/go/generic v0.0.0-20230721185457-c559e86c829c github.com/99designs/gqlgen v0.17.63 - github.com/Giulio2002/bls v0.0.0-20241116091023-2ddcc8954ec0 + github.com/Giulio2002/bls v0.0.0-20250218151206-daa74641714d github.com/Masterminds/sprig/v3 v3.2.3 github.com/RoaringBitmap/roaring/v2 v2.4.3 github.com/alecthomas/kong v0.8.1 diff --git a/go.sum b/go.sum index 93519c3131e..26004f2a69a 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/AskAlexSharov/bloomfilter/v2 v2.0.9 h1:BuZqNjRlYmcXJIsI7nrIkejYMz9mgF github.com/AskAlexSharov/bloomfilter/v2 v2.0.9/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Giulio2002/bls v0.0.0-20241116091023-2ddcc8954ec0 h1:6DVEDL29nd7f2GoHZIA9rjpW90gYeNE3x5aUadOgTB4= -github.com/Giulio2002/bls v0.0.0-20241116091023-2ddcc8954ec0/go.mod h1:k6OaCwpn4WGfzPgoXuEiWaV1BKXW+GjSkIz1mCA4jFU= +github.com/Giulio2002/bls v0.0.0-20250218151206-daa74641714d h1:OZwEfxKMk510XJnpOJXiP50mc9aPEPhHuwjpGG95JQs= +github.com/Giulio2002/bls v0.0.0-20250218151206-daa74641714d/go.mod h1:k6OaCwpn4WGfzPgoXuEiWaV1BKXW+GjSkIz1mCA4jFU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= From 7443f2acaf5787e46a2788b504304157071d006e Mon Sep 17 00:00:00 2001 From: lystopad Date: Thu, 20 Feb 2025 12:50:53 +0100 Subject: [PATCH 24/42] Bump builder and base docker images. (#13869) Go 1.23 Base image: debian:12-slim --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad04e922787..ad1337d24d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,8 +7,8 @@ env: TEST_TRACKING_TIME_SECONDS: 7200 # 2 hours TEST_TOTAL_TIME_SECONDS: 432000 # 5 days TEST_CHAIN: "mainnet" - BUILDER_IMAGE: "golang:1.22-bookworm" - DOCKER_BASE_IMAGE: "debian:12.8-slim" + BUILDER_IMAGE: "golang:1.23-bookworm" + DOCKER_BASE_IMAGE: "debian:12-slim" APP_REPO: "erigontech/erigon" PACKAGE: "github.com/erigontech/erigon" DOCKERHUB_REPOSITORY: "erigontech/erigon" From 716e474a16117be041f1808aa8e049f01ba80b45 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Thu, 20 Feb 2025 14:58:05 +0100 Subject: [PATCH 25/42] qa-tests: fix the cause of rpc tests failing intermittently (#13871) In this PR we try to save and then restore the previous chaindata. --- .github/workflows/qa-tip-tracking.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/qa-tip-tracking.yml b/.github/workflows/qa-tip-tracking.yml index e867c5577fc..a166f31d769 100644 --- a/.github/workflows/qa-tip-tracking.yml +++ b/.github/workflows/qa-tip-tracking.yml @@ -36,9 +36,10 @@ jobs: run: | python3 $ERIGON_QA_PATH/test_system/db-producer/pause_production.py || true - - name: Clean Erigon Chaindata Directory + - name: Save Erigon Chaindata Directory + id: save_chaindata_step run: | - rm -rf $ERIGON_REFERENCE_DATA_DIR/chaindata + mv $ERIGON_REFERENCE_DATA_DIR/chaindata $ERIGON_TESTBED_AREA/chaindata-prev - name: Run Erigon, wait sync and check ability to maintain sync id: test_step @@ -115,10 +116,11 @@ jobs: name: metric-plots path: ${{ github.workspace }}/metrics-${{ env.CHAIN }}-plots* - - name: Clean Erigon Chaindata Directory - if: always() + - name: Restore Erigon Chaindata Directory + if: steps.save_chaindata_step.outcome == 'success' run: | rm -rf $ERIGON_REFERENCE_DATA_DIR/chaindata + mv $ERIGON_TESTBED_AREA/chaindata-prev $ERIGON_REFERENCE_DATA_DIR/chaindata - name: Resume the Erigon instance dedicated to db maintenance run: | From 60f009e1b99b64e32c39c1b7cabbeb3c4702b290 Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Thu, 20 Feb 2025 16:19:26 +0100 Subject: [PATCH 26/42] Make err more specific (#13877) --- polygon/heimdall/client_http.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/polygon/heimdall/client_http.go b/polygon/heimdall/client_http.go index 877a80a7187..02bdd9094cb 100644 --- a/polygon/heimdall/client_http.go +++ b/polygon/heimdall/client_http.go @@ -46,12 +46,12 @@ var ( ErrNotInCheckpointList = errors.New("checkpontId doesn't exist in Heimdall") ErrBadGateway = errors.New("bad gateway") ErrServiceUnavailable = errors.New("service unavailable") - ErrCloudflareAccess = errors.New("cloudflare access") + ErrCloudflareAccessNoApp = errors.New("cloudflare access - no application") TransientErrors = []error{ ErrBadGateway, ErrServiceUnavailable, - ErrCloudflareAccess, + ErrCloudflareAccessNoApp, context.DeadlineExceeded, } ) @@ -686,10 +686,10 @@ func internalFetch(ctx context.Context, handler httpRequestHandler, u *url.URL, // check status code if res.StatusCode != 200 { - cloudflareErr := regexp.MustCompile(`Error.*Cloudflare Access`) + cloudflareErr := regexp.MustCompile(`Error.*Cloudflare Access.*Unable to find your Access application`) bodyStr := string(body) if res.StatusCode == 404 && cloudflareErr.MatchString(bodyStr) { - return nil, fmt.Errorf("%w: url='%s', status=%d, body='%s'", ErrCloudflareAccess, u.String(), res.StatusCode, bodyStr) + return nil, fmt.Errorf("%w: url='%s', status=%d, body='%s'", ErrCloudflareAccessNoApp, u.String(), res.StatusCode, bodyStr) } return nil, fmt.Errorf("%w: url='%s', status=%d, body='%s'", ErrNotSuccessfulResponse, u.String(), res.StatusCode, bodyStr) From 73df5ac1e8a6538a8f44185ed436c6de9af273b9 Mon Sep 17 00:00:00 2001 From: antonis19 Date: Thu, 20 Feb 2025 16:57:29 +0100 Subject: [PATCH 27/42] Make mdbx summaries map concurrent (#13888) Solves concurrency issues with MDBXSummaries global map. Co-authored-by: antonis19 --- erigon-lib/kv/kv_interface.go | 39 ++++++++++++++++++++++++++++------- erigon-lib/kv/mdbx/kv_mdbx.go | 9 ++++---- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/erigon-lib/kv/kv_interface.go b/erigon-lib/kv/kv_interface.go index eef32ed7c92..a8d96daa97a 100644 --- a/erigon-lib/kv/kv_interface.go +++ b/erigon-lib/kv/kv_interface.go @@ -19,12 +19,15 @@ package kv import ( "context" "errors" + "fmt" + "sync" "unsafe" "github.com/c2h5oh/datasize" "github.com/erigontech/erigon-lib/kv/order" "github.com/erigontech/erigon-lib/kv/stream" "github.com/erigontech/erigon-lib/metrics" + "github.com/erigontech/mdbx-go/mdbx" ) //Variables Naming: @@ -135,25 +138,45 @@ func InitMDBXMGauges() *DBGauges { // initialize summaries for a particular MDBX instance func InitSummaries(dbLabel Label) { // just in case the global singleton map is not already initialized - if MDBXSummaries == nil { - MDBXSummaries = make(map[Label]*DBSummaries) - } + // if MDBXSummaries == nil { + // MDBXSummaries = make(map[Label]*DBSummaries) + // } - _, ok := MDBXSummaries[dbLabel] + _, ok := MDBXSummaries.Load(dbLabel) if !ok { dbName := string(dbLabel) - MDBXSummaries[dbLabel] = &DBSummaries{ + MDBXSummaries.Store(dbName, &DBSummaries{ DbCommitPreparation: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "preparation"}), DbCommitWrite: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "write"}), DbCommitSync: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "sync"}), DbCommitEnding: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "ending"}), DbCommitTotal: metrics.GetOrCreateSummaryWithLabels(`db_commit_seconds`, []string{dbLabelName, "phase"}, []string{dbName, "total"}), - } + }) + } +} + +func RecordSummaries(dbLabel Label, latency mdbx.CommitLatency) error { + _summaries, ok := MDBXSummaries.Load(dbLabel) + if !ok { + return fmt.Errorf("MDBX summaries not initialized yet for db=%s", string(dbLabel)) + } + // cast to *DBSummaries + summaries, ok := _summaries.(*DBSummaries) + if !ok { + return fmt.Errorf("type casting to *DBSummaries failed") } + + summaries.DbCommitPreparation.Observe(latency.Preparation.Seconds()) + summaries.DbCommitWrite.Observe(latency.Write.Seconds()) + summaries.DbCommitSync.Observe(latency.Sync.Seconds()) + summaries.DbCommitEnding.Observe(latency.Ending.Seconds()) + summaries.DbCommitTotal.Observe(latency.Whole.Seconds()) + return nil + } -var MDBXGauges *DBGauges = InitMDBXMGauges() // global mdbx gauges. each gauge can be filtered by db name -var MDBXSummaries map[Label]*DBSummaries = make(map[Label]*DBSummaries) // dbName => Summaries mapping +var MDBXGauges *DBGauges = InitMDBXMGauges() // global mdbx gauges. each gauge can be filtered by db name +var MDBXSummaries sync.Map // dbName => Summaries mapping var ( ErrAttemptToDeleteNonDeprecatedBucket = errors.New("only buckets from dbutils.ChaindataDeprecatedTables can be deleted") diff --git a/erigon-lib/kv/mdbx/kv_mdbx.go b/erigon-lib/kv/mdbx/kv_mdbx.go index f3f7007da39..95bab7af69d 100644 --- a/erigon-lib/kv/mdbx/kv_mdbx.go +++ b/erigon-lib/kv/mdbx/kv_mdbx.go @@ -913,11 +913,10 @@ func (tx *MdbxTx) Commit() error { if tx.db.opts.metrics { dbLabel := tx.db.opts.label - kv.MDBXSummaries[dbLabel].DbCommitPreparation.Observe(latency.Preparation.Seconds()) - kv.MDBXSummaries[dbLabel].DbCommitWrite.Observe(latency.Write.Seconds()) - kv.MDBXSummaries[dbLabel].DbCommitSync.Observe(latency.Sync.Seconds()) - kv.MDBXSummaries[dbLabel].DbCommitEnding.Observe(latency.Ending.Seconds()) - kv.MDBXSummaries[dbLabel].DbCommitTotal.Observe(latency.Whole.Seconds()) + err = kv.RecordSummaries(dbLabel, latency) + if err != nil { + tx.db.opts.log.Error("failed to record mdbx summaries", "err", err) + } //kv.DbGcWorkPnlMergeTime.Update(latency.GCDetails.WorkPnlMergeTime.Seconds()) //kv.DbGcWorkPnlMergeVolume.Set(uint64(latency.GCDetails.WorkPnlMergeVolume)) From ac0de743ea6cb0998b7b789ffbb4d66ae4175feb Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Thu, 20 Feb 2025 20:32:59 +0100 Subject: [PATCH 28/42] Caplin: remove beacon blocks filtering (#13883) --- cl/phase1/stages/forward_sync.go | 44 +++++--------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/cl/phase1/stages/forward_sync.go b/cl/phase1/stages/forward_sync.go index 7bde4554c59..68ac8cba87e 100644 --- a/cl/phase1/stages/forward_sync.go +++ b/cl/phase1/stages/forward_sync.go @@ -101,30 +101,6 @@ func downloadAndProcessEip4844DA(ctx context.Context, logger log.Logger, cfg *Cf return highestProcessed - 1, err } -func filterUnneededBlocks(ctx context.Context, blocks []*cltypes.SignedBeaconBlock, cfg *Cfg) []*cltypes.SignedBeaconBlock { - filtered := make([]*cltypes.SignedBeaconBlock, 0, len(blocks)) - // Find the latest block in the list - for _, block := range blocks { - blockRoot, err := block.Block.HashSSZ() - if err != nil { - panic(err) - } - _, hasInFcu := cfg.forkChoice.GetHeader(blockRoot) - - var hasSignedHeaderInDB bool - if err = cfg.indiciesDB.View(ctx, func(tx kv.Tx) error { - _, hasSignedHeaderInDB, err = beacon_indicies.ReadSignedHeaderByBlockRoot(ctx, tx, blockRoot) - return err - }); err != nil { - panic(err) - } - if !hasInFcu || !hasSignedHeaderInDB { - filtered = append(filtered, block) - } - } - return filtered -} - // processDownloadedBlockBatches processes a batch of downloaded blocks. // It takes the highest block processed, a flag to determine if insertion is needed, and a list of signed beacon blocks as input. // It returns the new highest block processed and an error if any. @@ -134,12 +110,6 @@ func processDownloadedBlockBatches(ctx context.Context, logger log.Logger, cfg * return blocks[i].Block.Slot < blocks[j].Block.Slot }) - // Filter out blocks that are already in the FCU or have a signed header in the DB - blocks = filterUnneededBlocks(ctx, blocks, cfg) - if len(blocks) == 0 { - return highestBlockProcessed, nil - } - var ( blockRoot common.Hash st *state.CachingBeaconState @@ -228,16 +198,16 @@ func processDownloadedBlockBatches(ctx context.Context, logger log.Logger, cfg * // forwardSync (MAIN ROUTINE FOR ForwardSync) performs the forward synchronization of beacon blocks. func forwardSync(ctx context.Context, logger log.Logger, cfg *Cfg, args Args) error { var ( - shouldInsert = cfg.executionClient != nil && cfg.executionClient.SupportInsertion() // Check if the execution client supports insertion - finalizedCheckpoint = cfg.forkChoice.FinalizedCheckpoint() // Get the finalized checkpoint from fork choice - secsPerLog = 30 // Interval in seconds for logging progress - logTicker = time.NewTicker(time.Duration(secsPerLog) * time.Second) // Ticker for logging progress - downloader = network2.NewForwardBeaconDownloader(ctx, cfg.rpc) // Initialize a new forward beacon downloader - currentSlot atomic.Uint64 // Atomic variable to track the current slot + shouldInsert = cfg.executionClient != nil && cfg.executionClient.SupportInsertion() // Check if the execution client supports insertion + startSlot = cfg.forkChoice.HighestSeen() - 8 // Start forwardsync a little bit behind the highest seen slot (account for potential reorgs) + secsPerLog = 30 // Interval in seconds for logging progress + logTicker = time.NewTicker(time.Duration(secsPerLog) * time.Second) // Ticker for logging progress + downloader = network2.NewForwardBeaconDownloader(ctx, cfg.rpc) // Initialize a new forward beacon downloader + currentSlot atomic.Uint64 // Atomic variable to track the current slot ) // Initialize the slot to download from the finalized checkpoint - currentSlot.Store(finalizedCheckpoint.Epoch * cfg.beaconCfg.SlotsPerEpoch) + currentSlot.Store(startSlot) // Always start from the current finalized checkpoint downloader.SetHighestProcessedSlot(currentSlot.Load()) From 52a4392c63ee76a9e8f1f2013693f32758ed67d4 Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Thu, 20 Feb 2025 20:58:31 +0100 Subject: [PATCH 29/42] Handle potential overflow in getData (#13890) Fixes https://github.com/erigontech/security/issues/1 --- core/vm/common.go | 4 ++-- core/vm/contracts_test.go | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/vm/common.go b/core/vm/common.go index fb63750eb3d..e7421657bca 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -60,8 +60,8 @@ func getData(data []byte, start uint64, size uint64) []byte { if start > length { start = length } - end := start + size - if end > length { + end, overflow := math.SafeAdd(start, size) + if end > length || overflow { end = length } return common.RightPadBytes(data[start:end], int(size)) diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 09bb317087e..0bcbd6245ac 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -27,7 +27,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + libcommon "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon-lib/common/hexutil" + "github.com/erigontech/erigon-lib/common/math" ) // precompiledTest defines the input/output pairs for precompiled contract tests. @@ -260,6 +264,15 @@ func TestPrecompiledModExpOOG(t *testing.T) { } } +func TestModExpPrecompilePotentialOutOfRange(t *testing.T) { + modExpContract := PrecompiledContractsCancun[libcommon.BytesToAddress([]byte{0x05})] + hexString := "0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ee" + input := hexutil.MustDecode(hexString) + maxGas := uint64(math.MaxUint64) + _, _, err := RunPrecompiledContract(modExpContract, input, maxGas) + assert.NoError(t, err) +} + // Tests the sample inputs from the elliptic curve scalar multiplication EIP 213. func TestPrecompiledBn256ScalarMul(t *testing.T) { testJson("bn256ScalarMul", "07", t) } func BenchmarkPrecompiledBn256ScalarMul(b *testing.B) { benchJson("bn256ScalarMul", "07", b) } From 0ca5c6dfa1b759b673d0e16a9debe78ccd4effa0 Mon Sep 17 00:00:00 2001 From: Giulio rebuffo Date: Fri, 21 Feb 2025 04:09:44 +0100 Subject: [PATCH 30/42] Caplin: removed reusable states in reorg (not really used anymore) (#13893) --- cl/phase1/forkchoice/fork_graph/fork_graph_disk.go | 10 +++------- cl/phase1/forkchoice/fork_graph/fork_graph_disk_fs.go | 8 ++------ erigon-lib/common/dbg/experiments.go | 1 - 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/cl/phase1/forkchoice/fork_graph/fork_graph_disk.go b/cl/phase1/forkchoice/fork_graph/fork_graph_disk.go index 14011fec259..d50df8f7d1f 100644 --- a/cl/phase1/forkchoice/fork_graph/fork_graph_disk.go +++ b/cl/phase1/forkchoice/fork_graph/fork_graph_disk.go @@ -26,7 +26,6 @@ import ( "github.com/spf13/afero" libcommon "github.com/erigontech/erigon-lib/common" - "github.com/erigontech/erigon-lib/common/dbg" "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon/cl/beacon/beacon_router_configuration" "github.com/erigontech/erigon/cl/beacon/beaconevents" @@ -421,11 +420,8 @@ func (f *forkGraphDisk) getState(blockRoot libcommon.Hash, alwaysCopy bool, addC blocksInTheWay := []*cltypes.SignedBeaconBlock{} // Use the parent root as a reverse iterator. currentIteratorRoot := blockRoot - var copyReferencedState, outState *state.CachingBeaconState + var copyReferencedState *state.CachingBeaconState var err error - if addChainSegment && dbg.CaplinEfficientReorg { - outState = f.currentState - } // try and find the point of recconnection for copyReferencedState == nil { @@ -434,7 +430,7 @@ func (f *forkGraphDisk) getState(blockRoot libcommon.Hash, alwaysCopy bool, addC // check if it is in the header bHeader, ok := f.GetHeader(currentIteratorRoot) if ok && bHeader.Slot%dumpSlotFrequency == 0 { - copyReferencedState, err = f.readBeaconStateFromDisk(currentIteratorRoot, outState) + copyReferencedState, err = f.readBeaconStateFromDisk(currentIteratorRoot) if err != nil { log.Trace("Could not retrieve state: Missing header", "missing", currentIteratorRoot, "err", err) copyReferencedState = nil @@ -445,7 +441,7 @@ func (f *forkGraphDisk) getState(blockRoot libcommon.Hash, alwaysCopy bool, addC return nil, nil } if block.Block.Slot%dumpSlotFrequency == 0 { - copyReferencedState, err = f.readBeaconStateFromDisk(currentIteratorRoot, outState) + copyReferencedState, err = f.readBeaconStateFromDisk(currentIteratorRoot) if err != nil { log.Trace("Could not retrieve state: Missing header", "missing", currentIteratorRoot, "err", err) } diff --git a/cl/phase1/forkchoice/fork_graph/fork_graph_disk_fs.go b/cl/phase1/forkchoice/fork_graph/fork_graph_disk_fs.go index c670623fd8c..3ca261ef20e 100644 --- a/cl/phase1/forkchoice/fork_graph/fork_graph_disk_fs.go +++ b/cl/phase1/forkchoice/fork_graph/fork_graph_disk_fs.go @@ -34,7 +34,7 @@ func getBeaconStateFilename(blockRoot libcommon.Hash) string { return fmt.Sprintf("%x.snappy_ssz", blockRoot) } -func (f *forkGraphDisk) readBeaconStateFromDisk(blockRoot libcommon.Hash, out *state.CachingBeaconState) (bs *state.CachingBeaconState, err error) { +func (f *forkGraphDisk) readBeaconStateFromDisk(blockRoot libcommon.Hash) (bs *state.CachingBeaconState, err error) { var file afero.File f.stateDumpLock.Lock() defer f.stateDumpLock.Unlock() @@ -72,11 +72,7 @@ func (f *forkGraphDisk) readBeaconStateFromDisk(blockRoot libcommon.Hash, out *s return nil, fmt.Errorf("failed to read snappy buffer: %w, root: %x", err, blockRoot) } f.sszBuffer = f.sszBuffer[:n] - if out == nil { - bs = state.New(f.beaconCfg) - } else { - bs = out - } + bs = state.New(f.beaconCfg) if err = bs.DecodeSSZ(f.sszBuffer, int(v[0])); err != nil { return nil, fmt.Errorf("failed to decode beacon state: %w, root: %x, len: %d, decLen: %d, bs: %+v", err, blockRoot, n, len(f.sszBuffer), bs) diff --git a/erigon-lib/common/dbg/experiments.go b/erigon-lib/common/dbg/experiments.go index 64daf3f6ea6..167a4db34dd 100644 --- a/erigon-lib/common/dbg/experiments.go +++ b/erigon-lib/common/dbg/experiments.go @@ -67,7 +67,6 @@ var ( CommitEachStage = EnvBool("COMMIT_EACH_STAGE", false) CaplinSyncedDataMangerDeadlockDetection = EnvBool("CAPLIN_SYNCED_DATA_MANAGER_DEADLOCK_DETECTION", false) - CaplinEfficientReorg = EnvBool("CAPLIN_EFFICIENT_REORG", true) ) func ReadMemStats(m *runtime.MemStats) { From 5e54fe0cd68a347f840f032c70f558594a0925ee Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:44:44 +0100 Subject: [PATCH 31/42] Move msg.SetIsFree to core.ApplyMessage (#13880) See Issue #9259 --- accounts/abi/bind/backends/simulated.go | 1 + cmd/state/exec3/historical_trace_worker.go | 9 +---- cmd/state/exec3/state.go | 9 +---- cmd/state/exec3/trace_worker.go | 9 +---- core/blockchain.go | 18 ++++++--- core/state/txtask.go | 11 +++--- core/state_processor.go | 10 +---- core/state_transition.go | 17 ++++++++- core/types/access_list_tx.go | 6 +-- core/types/blob_tx.go | 8 ++-- core/types/blob_tx_wrapper.go | 2 +- core/types/dynamic_fee_tx.go | 8 ++-- core/types/legacy_tx.go | 4 +- core/types/set_code_tx.go | 10 ++--- core/types/transaction.go | 38 +++++++++---------- .../internal/tracetest/calltrace_test.go | 2 +- polygon/bridge/reader.go | 6 +-- polygon/tracer/trace_bor_state_sync_txn.go | 2 +- tests/state_test_util.go | 5 +-- turbo/adapter/ethapi/api.go | 16 ++++---- turbo/jsonrpc/eth_block.go | 2 +- turbo/jsonrpc/eth_call.go | 8 ++-- turbo/jsonrpc/eth_callMany.go | 4 +- turbo/jsonrpc/otterscan_api.go | 2 +- turbo/jsonrpc/otterscan_search_trace.go | 2 +- turbo/jsonrpc/overlay_api.go | 6 +-- .../receipts/bor_receipts_generator.go | 4 +- turbo/jsonrpc/trace_adhoc.go | 28 +++++++------- turbo/jsonrpc/trace_filtering.go | 24 ++---------- turbo/jsonrpc/tracing.go | 15 ++------ turbo/transactions/call.go | 7 ++-- turbo/transactions/tracing.go | 14 ++----- 32 files changed, 134 insertions(+), 173 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 24b9048ecda..e8bb54a6cd5 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -856,6 +856,7 @@ func (m callMsg) Data() []byte { return m.CallMsg.Data func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } func (m callMsg) Authorizations() []types.Authorization { return m.CallMsg.Authorizations } func (m callMsg) IsFree() bool { return false } +func (m callMsg) SetIsFree(_ bool) {} func (m callMsg) BlobGas() uint64 { return misc.GetBlobGasUsed(len(m.CallMsg.BlobHashes)) } func (m callMsg) MaxFeePerBlobGas() *uint256.Int { return m.CallMsg.MaxFeePerBlobGas } diff --git a/cmd/state/exec3/historical_trace_worker.go b/cmd/state/exec3/historical_trace_worker.go index 146807802b9..6be9a294eac 100644 --- a/cmd/state/exec3/historical_trace_worker.go +++ b/cmd/state/exec3/historical_trace_worker.go @@ -194,13 +194,6 @@ func (rw *HistoricalTraceWorker) RunTxTask(txTask *state.TxTask) { ibs.SetTxContext(txTask.TxIndex) msg := txTask.TxAsMessage msg.SetCheckNonce(!rw.vmConfig.StatelessExec) - if msg.FeeCap().IsZero() { - // Only zero-gas transactions may be service ones - syscall := func(contract common.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, rw.execArgs.ChainConfig, ibs, header, rw.execArgs.Engine, true /* constCall */) - } - msg.SetIsFree(rw.execArgs.Engine.IsServiceTransaction(msg.From(), syscall)) - } txContext := core.NewEVMTxContext(msg) if rw.vmConfig.TraceJumpDest { @@ -209,7 +202,7 @@ func (rw *HistoricalTraceWorker) RunTxTask(txTask *state.TxTask) { rw.evm.ResetBetweenBlocks(txTask.EvmBlockContext, txContext, ibs, *rw.vmConfig, rules) // MA applytx - applyRes, err := core.ApplyMessage(rw.evm, msg, rw.taskGasPool, true /* refunds */, false /* gasBailout */) + applyRes, err := core.ApplyMessage(rw.evm, msg, rw.taskGasPool, true /* refunds */, false /* gasBailout */, rw.execArgs.Engine) if err != nil { txTask.Error = err } else { diff --git a/cmd/state/exec3/state.go b/cmd/state/exec3/state.go index 852897ffa41..cbbe56f383a 100644 --- a/cmd/state/exec3/state.go +++ b/cmd/state/exec3/state.go @@ -268,18 +268,11 @@ func (rw *Worker) RunTxTaskNoLock(txTask *state.TxTask, isMining bool) { rw.vmCfg.SkipAnalysis = txTask.SkipAnalysis ibs.SetTxContext(txTask.TxIndex) msg := txTask.TxAsMessage - if msg.FeeCap().IsZero() && rw.engine != nil { - // Only zero-gas transactions may be service ones - syscall := func(contract libcommon.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, rw.chainConfig, ibs, header, rw.engine, true /* constCall */) - } - msg.SetIsFree(rw.engine.IsServiceTransaction(msg.From(), syscall)) - } rw.evm.ResetBetweenBlocks(txTask.EvmBlockContext, core.NewEVMTxContext(msg), ibs, rw.vmCfg, rules) // MA applytx - applyRes, err := core.ApplyMessage(rw.evm, msg, rw.taskGasPool, true /* refunds */, false /* gasBailout */) + applyRes, err := core.ApplyMessage(rw.evm, msg, rw.taskGasPool, true /* refunds */, false /* gasBailout */, rw.engine) if err != nil { txTask.Error = err } else { diff --git a/cmd/state/exec3/trace_worker.go b/cmd/state/exec3/trace_worker.go index 7b80c49992b..7a03ccd3097 100644 --- a/cmd/state/exec3/trace_worker.go +++ b/cmd/state/exec3/trace_worker.go @@ -114,13 +114,6 @@ func (e *TraceWorker) ExecTxn(txNum uint64, txIndex int, txn types.Transaction, return nil, err } msg.SetCheckNonce(!e.vmConfig.StatelessExec) - if msg.FeeCap().IsZero() { - // Only zero-gas transactions may be service ones - syscall := func(contract common.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, e.chainConfig, e.ibs, e.header, e.engine, true /* constCall */) - } - msg.SetIsFree(e.engine.IsServiceTransaction(msg.From(), syscall)) - } txContext := core.NewEVMTxContext(msg) if e.vmConfig.TraceJumpDest { @@ -129,7 +122,7 @@ func (e *TraceWorker) ExecTxn(txNum uint64, txIndex int, txn types.Transaction, e.evm.ResetBetweenBlocks(*e.blockCtx, txContext, e.ibs, *e.vmConfig, e.rules) gp := new(core.GasPool).AddGas(txn.GetGas()).AddBlobGas(txn.GetBlobGas()) - res, err := core.ApplyMessage(e.evm, msg, gp, true /* refunds */, gasBailout /* gasBailout */) + res, err := core.ApplyMessage(e.evm, msg, gp, true /* refunds */, gasBailout /* gasBailout */, e.engine) if err != nil { return nil, fmt.Errorf("%w: blockNum=%d, txNum=%d, %s", err, e.blockNum, txNum, e.ibs.Error()) } diff --git a/core/blockchain.go b/core/blockchain.go index 44fe8e0ac5a..f51d451f256 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -250,7 +250,19 @@ func rlpHash(x interface{}) (h libcommon.Hash) { return h } -func SysCallContract(contract libcommon.Address, data []byte, chainConfig *chain.Config, ibs *state.IntraBlockState, header *types.Header, engine consensus.EngineReader, constCall bool) (result []byte, err error) { +func SysCallContract(contract libcommon.Address, data []byte, chainConfig *chain.Config, ibs evmtypes.IntraBlockState, header *types.Header, engine consensus.EngineReader, constCall bool) (result []byte, err error) { + isBor := chainConfig.Bor != nil + var author *libcommon.Address + if isBor { + author = &header.Coinbase + } else { + author = &state.SystemAddress + } + blockContext := NewEVMBlockContext(header, GetHashFn(header, nil), engine, author, chainConfig) + return SysCallContractWithBlockContext(contract, data, chainConfig, ibs, blockContext, engine, constCall) +} + +func SysCallContractWithBlockContext(contract libcommon.Address, data []byte, chainConfig *chain.Config, ibs evmtypes.IntraBlockState, blockContext evmtypes.BlockContext, engine consensus.EngineReader, constCall bool) (result []byte, err error) { msg := types.NewMessage( state.SystemAddress, &contract, @@ -266,15 +278,11 @@ func SysCallContract(contract libcommon.Address, data []byte, chainConfig *chain // Create a new context to be used in the EVM environment isBor := chainConfig.Bor != nil var txContext evmtypes.TxContext - var author *libcommon.Address if isBor { - author = &header.Coinbase txContext = evmtypes.TxContext{} } else { - author = &state.SystemAddress txContext = NewEVMTxContext(msg) } - blockContext := NewEVMBlockContext(header, GetHashFn(header, nil), engine, author, chainConfig) evm := vm.NewEVM(blockContext, txContext, ibs, chainConfig, vmConfig) ret, _, err := evm.Call( diff --git a/core/state/txtask.go b/core/state/txtask.go index af38e935e2a..af30192240d 100644 --- a/core/state/txtask.go +++ b/core/state/txtask.go @@ -23,17 +23,16 @@ import ( "sync" "time" - "github.com/erigontech/erigon-lib/common/dbg" - "github.com/erigontech/erigon-lib/log/v3" - - "github.com/erigontech/erigon-lib/kv" - "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" "github.com/holiman/uint256" "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-lib/state" "github.com/erigontech/erigon-lib/types/accounts" + "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/core/vm/evmtypes" ) @@ -58,7 +57,7 @@ type TxTask struct { Failed bool Tx types.Transaction GetHashFn func(n uint64) libcommon.Hash - TxAsMessage types.Message + TxAsMessage *types.Message EvmBlockContext evmtypes.BlockContext HistoryExecution bool // use history reader for that txn instead of state reader diff --git a/core/state_processor.go b/core/state_processor.go index 43cebae1845..a18dd25c9de 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -45,14 +45,6 @@ func applyTransaction(config *chain.Config, engine consensus.EngineReader, gp *G } msg.SetCheckNonce(!cfg.StatelessExec) - if msg.FeeCap().IsZero() && engine != nil { - // Only zero-gas transactions may be service ones - syscall := func(contract libcommon.Address, data []byte) ([]byte, error) { - return SysCallContract(contract, data, config, ibs, header, engine, true /* constCall */) - } - msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) - } - txContext := NewEVMTxContext(msg) if cfg.TraceJumpDest { txContext.TxHash = txn.Hash() @@ -60,7 +52,7 @@ func applyTransaction(config *chain.Config, engine consensus.EngineReader, gp *G // Update the evm with the new transaction context. evm.Reset(txContext, ibs) - result, err := ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + result, err := ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, engine) if err != nil { return nil, nil, err } diff --git a/core/state_transition.go b/core/state_transition.go index c241ec96d83..5d3925a58d5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -33,6 +33,8 @@ import ( "github.com/erigontech/erigon-lib/common/u256" "github.com/erigontech/erigon-lib/crypto" "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon/consensus" + "github.com/erigontech/erigon/core/state" "github.com/erigontech/erigon/core/tracing" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/core/vm" @@ -102,7 +104,8 @@ type Message interface { BlobHashes() []libcommon.Hash Authorizations() []types.Authorization - IsFree() bool + IsFree() bool // service transactions on Gnosis are exempt from EIP-1559 mandatory fees + SetIsFree(bool) } // NewStateTransition initialises and returns a new state transition object. @@ -133,7 +136,17 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // `refunds` is false when it is not required to apply gas refunds // `gasBailout` is true when it is not required to fail transaction if the balance is not enough to pay gas. // for trace_call to replicate OE/Parity behaviour -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool, refunds bool, gasBailout bool) (*evmtypes.ExecutionResult, error) { +func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool, refunds bool, gasBailout bool, engine consensus.EngineReader) ( + *evmtypes.ExecutionResult, error) { + // Only zero-gas transactions may be service ones + if msg.FeeCap().IsZero() && !msg.IsFree() && engine != nil { + blockContext := evm.Context + blockContext.Coinbase = state.SystemAddress + syscall := func(contract libcommon.Address, data []byte) ([]byte, error) { + return SysCallContractWithBlockContext(contract, data, evm.ChainConfig(), evm.IntraBlockState(), blockContext, engine, true /* constCall */) + } + msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) + } return NewStateTransition(evm, msg, gp).TransitionDb(refunds, gasBailout) } diff --git a/core/types/access_list_tx.go b/core/types/access_list_tx.go index d4484f33388..34b51485964 100644 --- a/core/types/access_list_tx.go +++ b/core/types/access_list_tx.go @@ -411,7 +411,7 @@ func (tx *AccessListTx) DecodeRLP(s *rlp.Stream) error { } // AsMessage returns the transaction as a core.Message. -func (tx *AccessListTx) AsMessage(s Signer, _ *big.Int, rules *chain.Rules) (Message, error) { +func (tx *AccessListTx) AsMessage(s Signer, _ *big.Int, rules *chain.Rules) (*Message, error) { msg := Message{ nonce: tx.Nonce, gasLimit: tx.Gas, @@ -426,12 +426,12 @@ func (tx *AccessListTx) AsMessage(s Signer, _ *big.Int, rules *chain.Rules) (Mes } if !rules.IsBerlin { - return msg, errors.New("eip-2930 transactions require Berlin") + return nil, errors.New("eip-2930 transactions require Berlin") } var err error msg.from, err = tx.Sender(s) - return msg, err + return &msg, err } func (tx *AccessListTx) WithSignature(signer Signer, sig []byte) (Transaction, error) { diff --git a/core/types/blob_tx.go b/core/types/blob_tx.go index 08a6db83af6..1d662c5e846 100644 --- a/core/types/blob_tx.go +++ b/core/types/blob_tx.go @@ -62,7 +62,7 @@ func (stx *BlobTx) GetBlobGas() uint64 { return fixedgas.BlobGasPerBlob * uint64(len(stx.BlobVersionedHashes)) } -func (stx *BlobTx) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Message, error) { +func (stx *BlobTx) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (*Message, error) { msg := Message{ nonce: stx.Nonce, gasLimit: stx.Gas, @@ -76,12 +76,12 @@ func (stx *BlobTx) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Me checkNonce: true, } if !rules.IsCancun { - return msg, errors.New("BlobTx transactions require Cancun") + return nil, errors.New("BlobTx transactions require Cancun") } if baseFee != nil { overflow := msg.gasPrice.SetFromBig(baseFee) if overflow { - return msg, errors.New("gasPrice higher than 2^256-1") + return nil, errors.New("gasPrice higher than 2^256-1") } } msg.gasPrice.Add(&msg.gasPrice, stx.Tip) @@ -92,7 +92,7 @@ func (stx *BlobTx) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Me msg.from, err = stx.Sender(s) msg.maxFeePerBlobGas = *stx.MaxFeePerBlobGas msg.blobHashes = stx.BlobVersionedHashes - return msg, err + return &msg, err } func (stx *BlobTx) cachedSender() (sender libcommon.Address, ok bool) { diff --git a/core/types/blob_tx_wrapper.go b/core/types/blob_tx_wrapper.go index 41be050d167..e444fcfc413 100644 --- a/core/types/blob_tx_wrapper.go +++ b/core/types/blob_tx_wrapper.go @@ -312,7 +312,7 @@ func (txw *BlobTxWrapper) GetBlobGas() uint64 { return txw.Tx.GetBlobGas( func (txw *BlobTxWrapper) GetValue() *uint256.Int { return txw.Tx.GetValue() } func (txw *BlobTxWrapper) GetTo() *libcommon.Address { return txw.Tx.GetTo() } -func (txw *BlobTxWrapper) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Message, error) { +func (txw *BlobTxWrapper) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (*Message, error) { return txw.Tx.AsMessage(s, baseFee, rules) } func (txw *BlobTxWrapper) WithSignature(signer Signer, sig []byte) (Transaction, error) { diff --git a/core/types/dynamic_fee_tx.go b/core/types/dynamic_fee_tx.go index a165b0109c5..bda76e3a5a6 100644 --- a/core/types/dynamic_fee_tx.go +++ b/core/types/dynamic_fee_tx.go @@ -329,7 +329,7 @@ func (tx *DynamicFeeTransaction) DecodeRLP(s *rlp.Stream) error { } // AsMessage returns the transaction as a core.Message. -func (tx *DynamicFeeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Message, error) { +func (tx *DynamicFeeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (*Message, error) { msg := Message{ nonce: tx.Nonce, gasLimit: tx.Gas, @@ -343,12 +343,12 @@ func (tx *DynamicFeeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *ch checkNonce: true, } if !rules.IsLondon { - return msg, errors.New("eip-1559 transactions require London") + return nil, errors.New("eip-1559 transactions require London") } if baseFee != nil { overflow := msg.gasPrice.SetFromBig(baseFee) if overflow { - return msg, errors.New("gasPrice higher than 2^256-1") + return nil, errors.New("gasPrice higher than 2^256-1") } } msg.gasPrice.Add(&msg.gasPrice, tx.Tip) @@ -358,7 +358,7 @@ func (tx *DynamicFeeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *ch var err error msg.from, err = tx.Sender(s) - return msg, err + return &msg, err } // Hash computes the hash (but not for signatures!) diff --git a/core/types/legacy_tx.go b/core/types/legacy_tx.go index fb57987566f..62781f534f7 100644 --- a/core/types/legacy_tx.go +++ b/core/types/legacy_tx.go @@ -343,7 +343,7 @@ func (tx *LegacyTx) DecodeRLP(s *rlp.Stream) error { } // AsMessage returns the transaction as a core.Message. -func (tx *LegacyTx) AsMessage(s Signer, _ *big.Int, _ *chain.Rules) (Message, error) { +func (tx *LegacyTx) AsMessage(s Signer, _ *big.Int, _ *chain.Rules) (*Message, error) { msg := Message{ nonce: tx.Nonce, gasLimit: tx.Gas, @@ -359,7 +359,7 @@ func (tx *LegacyTx) AsMessage(s Signer, _ *big.Int, _ *chain.Rules) (Message, er var err error msg.from, err = tx.Sender(s) - return msg, err + return &msg, err } func (tx *LegacyTx) WithSignature(signer Signer, sig []byte) (Transaction, error) { diff --git a/core/types/set_code_tx.go b/core/types/set_code_tx.go index 85edb0a53bb..adc004003be 100644 --- a/core/types/set_code_tx.go +++ b/core/types/set_code_tx.go @@ -114,7 +114,7 @@ func (tx *SetCodeTransaction) MarshalBinary(w io.Writer) error { return nil } -func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Message, error) { +func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (*Message, error) { msg := Message{ nonce: tx.Nonce, gasLimit: tx.Gas, @@ -128,12 +128,12 @@ func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain checkNonce: true, } if !rules.IsPrague { - return msg, errors.New("SetCodeTransaction is only supported in Prague") + return nil, errors.New("SetCodeTransaction is only supported in Prague") } if baseFee != nil { overflow := msg.gasPrice.SetFromBig(baseFee) if overflow { - return msg, errors.New("gasPrice higher than 2^256-1") + return nil, errors.New("gasPrice higher than 2^256-1") } } msg.gasPrice.Add(&msg.gasPrice, tx.Tip) @@ -142,13 +142,13 @@ func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain } if len(tx.Authorizations) == 0 { - return msg, errors.New("SetCodeTransaction without authorizations is invalid") + return nil, errors.New("SetCodeTransaction without authorizations is invalid") } msg.authorizations = tx.Authorizations var err error msg.from, err = tx.Sender(s) - return msg, err + return &msg, err } func (tx *SetCodeTransaction) Sender(signer Signer) (libcommon.Address, error) { diff --git a/core/types/transaction.go b/core/types/transaction.go index caf686184ee..9356b1e9964 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -69,7 +69,7 @@ type Transaction interface { GetBlobGas() uint64 GetValue() *uint256.Int GetTo() *libcommon.Address - AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Message, error) + AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (*Message, error) WithSignature(signer Signer, sig []byte) (Transaction, error) Hash() libcommon.Hash SigningHash(chainID *big.Int) libcommon.Hash @@ -376,7 +376,7 @@ type Message struct { func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amount *uint256.Int, gasLimit uint64, gasPrice *uint256.Int, feeCap, tip *uint256.Int, data []byte, accessList AccessList, checkNonce bool, isFree bool, maxFeePerBlobGas *uint256.Int, -) Message { +) *Message { m := Message{ from: from, to: to, @@ -400,28 +400,28 @@ func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amo if maxFeePerBlobGas != nil { m.maxFeePerBlobGas.Set(maxFeePerBlobGas) } - return m + return &m } -func (m Message) From() libcommon.Address { return m.from } -func (m Message) To() *libcommon.Address { return m.to } -func (m Message) GasPrice() *uint256.Int { return &m.gasPrice } -func (m Message) FeeCap() *uint256.Int { return &m.feeCap } -func (m Message) Tip() *uint256.Int { return &m.tip } -func (m Message) Value() *uint256.Int { return &m.amount } -func (m Message) Gas() uint64 { return m.gasLimit } -func (m Message) Nonce() uint64 { return m.nonce } -func (m Message) Data() []byte { return m.data } -func (m Message) AccessList() AccessList { return m.accessList } -func (m Message) Authorizations() []Authorization { return m.authorizations } +func (m *Message) From() libcommon.Address { return m.from } +func (m *Message) To() *libcommon.Address { return m.to } +func (m *Message) GasPrice() *uint256.Int { return &m.gasPrice } +func (m *Message) FeeCap() *uint256.Int { return &m.feeCap } +func (m *Message) Tip() *uint256.Int { return &m.tip } +func (m *Message) Value() *uint256.Int { return &m.amount } +func (m *Message) Gas() uint64 { return m.gasLimit } +func (m *Message) Nonce() uint64 { return m.nonce } +func (m *Message) Data() []byte { return m.data } +func (m *Message) AccessList() AccessList { return m.accessList } +func (m *Message) Authorizations() []Authorization { return m.authorizations } func (m *Message) SetAuthorizations(authorizations []Authorization) { m.authorizations = authorizations } -func (m Message) CheckNonce() bool { return m.checkNonce } +func (m *Message) CheckNonce() bool { return m.checkNonce } func (m *Message) SetCheckNonce(checkNonce bool) { m.checkNonce = checkNonce } -func (m Message) IsFree() bool { return m.isFree } +func (m *Message) IsFree() bool { return m.isFree } func (m *Message) SetIsFree(isFree bool) { m.isFree = isFree } @@ -442,13 +442,13 @@ func (m *Message) ChangeGas(globalGasCap, desiredGas uint64) { m.gasLimit = gas } -func (m Message) BlobGas() uint64 { return fixedgas.BlobGasPerBlob * uint64(len(m.blobHashes)) } +func (m *Message) BlobGas() uint64 { return fixedgas.BlobGasPerBlob * uint64(len(m.blobHashes)) } -func (m Message) MaxFeePerBlobGas() *uint256.Int { +func (m *Message) MaxFeePerBlobGas() *uint256.Int { return &m.maxFeePerBlobGas } -func (m Message) BlobHashes() []libcommon.Hash { return m.blobHashes } +func (m *Message) BlobHashes() []libcommon.Hash { return m.blobHashes } func DecodeSSZ(data []byte, dest codec.Deserializable) error { err := dest.Deserialize(codec.NewDecodingReader(bytes.NewReader(data), uint64(len(data)))) diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 087d0ad2312..19404e70d39 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -165,7 +165,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { } txContext := core.NewEVMTxContext(msg) evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) - vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas()), true /* refunds */, false /* gasBailout */) + vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.GetGas()).AddBlobGas(tx.GetBlobGas()), true /* refunds */, false /* gasBailout */, nil /* engine */) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } diff --git a/polygon/bridge/reader.go b/polygon/bridge/reader.go index 87e07a9ca6a..b6de1268e2d 100644 --- a/polygon/bridge/reader.go +++ b/polygon/bridge/reader.go @@ -105,7 +105,7 @@ func (r *Reader) Events(ctx context.Context, blockNum uint64) ([]*types.Message, nil, ) - eventsRaw = append(eventsRaw, &msg) + eventsRaw = append(eventsRaw, msg) } return eventsRaw, nil @@ -196,7 +196,7 @@ func messageFromData(to libcommon.Address, data []byte) *types.Message { nil, ) - return &msg + return msg } // NewStateSyncEventMessages creates a corresponding message that can be passed to EVM for multiple state sync events @@ -219,7 +219,7 @@ func NewStateSyncEventMessages(stateSyncEvents []rlp.RawValue, stateReceiverCont nil, // maxFeePerBlobGas ) - msgs[i] = &msg + msgs[i] = msg } return msgs diff --git a/polygon/tracer/trace_bor_state_sync_txn.go b/polygon/tracer/trace_bor_state_sync_txn.go index a032b28153c..cafab3f2ad1 100644 --- a/polygon/tracer/trace_bor_state_sync_txn.go +++ b/polygon/tracer/trace_bor_state_sync_txn.go @@ -117,7 +117,7 @@ func traceBorStateSyncTxn( } gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) - _, err := core.ApplyMessage(evm, msg, gp, refunds, false /* gasBailout */) + _, err := core.ApplyMessage(evm, msg, gp, refunds, false /* gasBailout */, nil /* engine */) if err != nil { return nil, err } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 1727cd7bc6b..292f4d16440 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -41,10 +41,9 @@ import ( "github.com/erigontech/erigon-lib/crypto" "github.com/erigontech/erigon-lib/kv" "github.com/erigontech/erigon-lib/log/v3" + "github.com/erigontech/erigon-lib/rlp" state2 "github.com/erigontech/erigon-lib/state" "github.com/erigontech/erigon-lib/wrap" - - "github.com/erigontech/erigon-lib/rlp" "github.com/erigontech/erigon/consensus/misc" "github.com/erigontech/erigon/core" "github.com/erigontech/erigon/core/state" @@ -270,7 +269,7 @@ func (t *StateTest) RunNoVerify(tx kv.RwTx, subtest StateSubtest, vmconfig vm.Co snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()).AddBlobGas(config.GetMaxBlobGasPerBlock(header.Time)) - if _, err = core.ApplyMessage(evm, msg, gaspool, true /* refunds */, false /* gasBailout */); err != nil { + if _, err = core.ApplyMessage(evm, msg, gaspool, true /* refunds */, false /* gasBailout */, nil /* engine */); err != nil { statedb.RevertToSnapshot(snapshot) } diff --git a/turbo/adapter/ethapi/api.go b/turbo/adapter/ethapi/api.go index fb4360d11a7..243c3631f8d 100644 --- a/turbo/adapter/ethapi/api.go +++ b/turbo/adapter/ethapi/api.go @@ -63,10 +63,10 @@ func (arg *CallArgs) from() libcommon.Address { } // ToMessage converts CallArgs to the Message type used by the core evm -func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (types.Message, error) { +func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (*types.Message, error) { // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { - return types.Message{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") + return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } // Set sender address or use zero address if none specified. addr := args.from() @@ -96,7 +96,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type if args.GasPrice != nil { overflow := gasPrice.SetFromBig(args.GasPrice.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } } gasFeeCap, gasTipCap = gasPrice, gasPrice @@ -107,7 +107,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type gasPrice = new(uint256.Int) overflow := gasPrice.SetFromBig(args.GasPrice.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } gasFeeCap, gasTipCap = gasPrice, gasPrice } else { @@ -116,14 +116,14 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type if args.MaxFeePerGas != nil { overflow := gasFeeCap.SetFromBig(args.MaxFeePerGas.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } } gasTipCap = new(uint256.Int) if args.MaxPriorityFeePerGas != nil { overflow := gasTipCap.SetFromBig(args.MaxPriorityFeePerGas.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } } // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes @@ -135,7 +135,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type if args.MaxFeePerBlobGas != nil { blobFee, overflow := uint256.FromBig(args.MaxFeePerBlobGas.ToInt()) if overflow { - return types.Message{}, errors.New("args.MaxFeePerBlobGas higher than 2^256-1") + return nil, errors.New("args.MaxFeePerBlobGas higher than 2^256-1") } maxFeePerBlobGas = blobFee } @@ -145,7 +145,7 @@ func (args *CallArgs) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (type if args.Value != nil { overflow := value.SetFromBig(args.Value.ToInt()) if overflow { - return types.Message{}, errors.New("args.Value higher than 2^256-1") + return nil, errors.New("args.Value higher than 2^256-1") } } var data []byte diff --git a/turbo/jsonrpc/eth_block.go b/turbo/jsonrpc/eth_block.go index 02af78f9e11..bb8d0ed0fa4 100644 --- a/turbo/jsonrpc/eth_block.go +++ b/turbo/jsonrpc/eth_block.go @@ -180,7 +180,7 @@ func (api *APIImpl) CallBundle(ctx context.Context, txHashes []common.Hash, stat return nil, err } // Execute the transaction message - result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, engine) if err != nil { return nil, err } diff --git a/turbo/jsonrpc/eth_call.go b/turbo/jsonrpc/eth_call.go index d0f982c526b..7aed0ebab2f 100644 --- a/turbo/jsonrpc/eth_call.go +++ b/turbo/jsonrpc/eth_call.go @@ -280,7 +280,7 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, *evmtypes.ExecutionResult, error) { - result, err := caller.DoCallWithNewGas(ctx, gas) + result, err := caller.DoCallWithNewGas(ctx, gas, engine) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { // Special case, raise gas limit @@ -838,15 +838,13 @@ func (api *APIImpl) CreateAccessList(ctx context.Context, args ethapi2.CallArgs, // Set the accesslist to the last al args.AccessList = &accessList - var msg types.Message - var baseFee *uint256.Int = nil // check if EIP-1559 if header.BaseFee != nil { baseFee, _ = uint256.FromBig(header.BaseFee) } - msg, err = args.ToMessage(api.GasCap, baseFee) + msg, err := args.ToMessage(api.GasCap, baseFee) if err != nil { return nil, err } @@ -859,7 +857,7 @@ func (api *APIImpl) CreateAccessList(ctx context.Context, args ethapi2.CallArgs, evm := vm.NewEVM(blockCtx, txCtx, state, chainConfig, config) gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) - res, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + res, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, engine) if err != nil { return nil, err } diff --git a/turbo/jsonrpc/eth_callMany.go b/turbo/jsonrpc/eth_callMany.go index c1725860bd2..091414eb7f7 100644 --- a/turbo/jsonrpc/eth_callMany.go +++ b/turbo/jsonrpc/eth_callMany.go @@ -217,7 +217,7 @@ func (api *APIImpl) CallMany(ctx context.Context, bundles []Bundle, simulateCont txCtx = core.NewEVMTxContext(msg) evm = vm.NewEVM(blockCtx, txCtx, evm.IntraBlockState(), chainConfig, vm.Config{Debug: false}) // Execute the transaction message - _, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + _, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, api.engine()) if err != nil { return nil, err } @@ -277,7 +277,7 @@ func (api *APIImpl) CallMany(ctx context.Context, bundles []Bundle, simulateCont } txCtx = core.NewEVMTxContext(msg) evm = vm.NewEVM(blockCtx, txCtx, evm.IntraBlockState(), chainConfig, vm.Config{Debug: false}) - result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, api.engine()) if err != nil { return nil, err } diff --git a/turbo/jsonrpc/otterscan_api.go b/turbo/jsonrpc/otterscan_api.go index b2a9af1cbeb..55627045443 100644 --- a/turbo/jsonrpc/otterscan_api.go +++ b/turbo/jsonrpc/otterscan_api.go @@ -163,7 +163,7 @@ func (api *OtterscanAPIImpl) runTracer(ctx context.Context, tx kv.TemporalTx, ha } vmenv := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig) - result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()), true, false /* gasBailout */) + result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()), true, false /* gasBailout */, engine) if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } diff --git a/turbo/jsonrpc/otterscan_search_trace.go b/turbo/jsonrpc/otterscan_search_trace.go index 2e2ca3909db..f96bd74f700 100644 --- a/turbo/jsonrpc/otterscan_search_trace.go +++ b/turbo/jsonrpc/otterscan_search_trace.go @@ -121,7 +121,7 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.TemporalTx, ctx context.Context, TxContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(BlockContext, TxContext, ibs, chainConfig, vm.Config{Debug: true, Tracer: tracer}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(txn.GetGas()).AddBlobGas(txn.GetBlobGas()), true /* refunds */, false /* gasBailout */); err != nil { + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(txn.GetGas()).AddBlobGas(txn.GetBlobGas()), true /* refunds */, false /* gasBailout */, engine); err != nil { return false, nil, err } _ = ibs.FinalizeTx(rules, cachedWriter) diff --git a/turbo/jsonrpc/overlay_api.go b/turbo/jsonrpc/overlay_api.go index 4396c13d754..45fbdaf86bd 100644 --- a/turbo/jsonrpc/overlay_api.go +++ b/turbo/jsonrpc/overlay_api.go @@ -193,7 +193,7 @@ func (api *OverlayAPIImpl) CallConstructor(ctx context.Context, address common.A txCtx = core.NewEVMTxContext(msg) evm = vm.NewEVM(blockCtx, txCtx, evm.IntraBlockState(), chainConfig, vm.Config{Debug: false}) // Execute the transaction message - _, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + _, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, api.engine()) if err != nil { return nil, err } @@ -222,7 +222,7 @@ func (api *OverlayAPIImpl) CallConstructor(ctx context.Context, address common.A evm = vm.NewEVM(blockCtx, txCtx, evm.IntraBlockState(), chainConfig, vm.Config{Debug: true, Tracer: &ct}) // Execute the transaction message - _, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */) + _, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */, api.engine()) if ct.err != nil { return nil, err } @@ -528,7 +528,7 @@ func (api *OverlayAPIImpl) replayBlock(ctx context.Context, blockNum uint64, sta evm.TxContext = txCtx // Execute the transaction message - res, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */) + res, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */, api.engine()) if err != nil { log.Error(err.Error()) return nil, err diff --git a/turbo/jsonrpc/receipts/bor_receipts_generator.go b/turbo/jsonrpc/receipts/bor_receipts_generator.go index 0a0ba66a458..26a7a83be66 100644 --- a/turbo/jsonrpc/receipts/bor_receipts_generator.go +++ b/turbo/jsonrpc/receipts/bor_receipts_generator.go @@ -2,7 +2,6 @@ package receipts import ( "context" - "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" lru "github.com/hashicorp/golang-lru/v2" @@ -12,6 +11,7 @@ import ( "github.com/erigontech/erigon-lib/kv/rawdbv3" "github.com/erigontech/erigon/consensus" "github.com/erigontech/erigon/core" + "github.com/erigontech/erigon/core/rawdb/rawtemporaldb" "github.com/erigontech/erigon/core/state" "github.com/erigontech/erigon/core/types" "github.com/erigontech/erigon/core/vm" @@ -82,7 +82,7 @@ func applyBorTransaction(msgs []*types.Message, evm *vm.EVM, gp *core.GasPool, i txContext := core.NewEVMTxContext(msg) evm.Reset(txContext, ibs) - _, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + _, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, nil /* engine */) if err != nil { return nil, err } diff --git a/turbo/jsonrpc/trace_adhoc.go b/turbo/jsonrpc/trace_adhoc.go index 6608df25f79..4368d7c778d 100644 --- a/turbo/jsonrpc/trace_adhoc.go +++ b/turbo/jsonrpc/trace_adhoc.go @@ -22,8 +22,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/erigontech/erigon-lib/kv/rawdbv3" - "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" "math" "strings" @@ -33,6 +31,7 @@ import ( "github.com/erigontech/erigon-lib/common/hexutil" math2 "github.com/erigontech/erigon-lib/common/math" "github.com/erigontech/erigon-lib/kv" + "github.com/erigontech/erigon-lib/kv/rawdbv3" "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon-lib/types/accounts" "github.com/erigontech/erigon/core" @@ -45,6 +44,7 @@ import ( "github.com/erigontech/erigon/rpc" "github.com/erigontech/erigon/turbo/rpchelper" "github.com/erigontech/erigon/turbo/shards" + "github.com/erigontech/erigon/turbo/snapshotsync/freezeblocks" "github.com/erigontech/erigon/turbo/transactions" ) @@ -149,7 +149,7 @@ type VmTraceStore struct { } // ToMessage converts CallArgs to the Message type used by the core evm -func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (types.Message, error) { +func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) (*types.Message, error) { // Set sender address or use zero address if none specified. var addr libcommon.Address if args.From != nil { @@ -180,7 +180,7 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) if args.GasPrice != nil { overflow := gasPrice.SetFromBig(args.GasPrice.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } } gasFeeCap, gasTipCap = gasPrice, gasPrice @@ -191,7 +191,7 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) // User specified the legacy gas field, convert to 1559 gas typing gasPrice, overflow = uint256.FromBig(args.GasPrice.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } gasFeeCap, gasTipCap = gasPrice, gasPrice } else { @@ -200,14 +200,14 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) if args.MaxFeePerGas != nil { overflow := gasFeeCap.SetFromBig(args.MaxFeePerGas.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } } gasTipCap = new(uint256.Int) if args.MaxPriorityFeePerGas != nil { overflow := gasTipCap.SetFromBig(args.MaxPriorityFeePerGas.ToInt()) if overflow { - return types.Message{}, errors.New("args.GasPrice higher than 2^256-1") + return nil, errors.New("args.GasPrice higher than 2^256-1") } } // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes @@ -228,7 +228,7 @@ func (args *TraceCallParam) ToMessage(globalGasCap uint64, baseFee *uint256.Int) if args.Value != nil { overflow := value.SetFromBig(args.Value.ToInt()) if overflow { - return types.Message{}, errors.New("args.Value higher than 2^256-1") + return nil, errors.New("args.Value higher than 2^256-1") } } var data []byte @@ -1093,7 +1093,7 @@ func (api *TraceAPIImpl) Call(ctx context.Context, args TraceCallParam, traceTyp gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) var execResult *evmtypes.ExecutionResult ibs.SetTxContext(0) - execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */) + execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, true /* gasBailout */, engine) if err != nil { return nil, err } @@ -1192,7 +1192,7 @@ func (api *TraceAPIImpl) CallMany(ctx context.Context, calls json.RawMessage, pa return nil, errors.New("header.BaseFee uint256 overflow") } } - msgs := make([]types.Message, len(callParams)) + msgs := make([]*types.Message, len(callParams)) for i, args := range callParams { msgs[i], err = args.ToMessage(api.gasCap, baseFee) if err != nil { @@ -1221,7 +1221,7 @@ func (api *TraceAPIImpl) CallMany(ctx context.Context, calls json.RawMessage, pa func (api *TraceAPIImpl) doCallBlock(ctx context.Context, dbtx kv.Tx, stateReader state.StateReader, stateCache *shards.StateCache, cachedWriter state.StateWriter, ibs *state.IntraBlockState, - msgs []types.Message, callParams []TraceCallParam, + msgs []*types.Message, callParams []TraceCallParam, parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header, gasBailout bool, traceConfig *config.TraceConfig, ) ([]*TraceCallResult, error) { @@ -1377,7 +1377,7 @@ func (api *TraceAPIImpl) doCallBlock(ctx context.Context, dbtx kv.Tx, stateReade evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig) gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) - execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /*gasBailout*/) + execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /*gasBailout*/, engine) } if err != nil { return nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err) @@ -1421,7 +1421,7 @@ func (api *TraceAPIImpl) doCallBlock(ctx context.Context, dbtx kv.Tx, stateReade func (api *TraceAPIImpl) doCall(ctx context.Context, dbtx kv.Tx, stateReader state.StateReader, stateCache *shards.StateCache, cachedWriter state.StateWriter, ibs *state.IntraBlockState, - msg types.Message, callParam TraceCallParam, + msg *types.Message, callParam TraceCallParam, parentNrOrHash *rpc.BlockNumberOrHash, header *types.Header, gasBailout bool, txIndex int, traceConfig *config.TraceConfig, ) (*TraceCallResult, error) { @@ -1575,7 +1575,7 @@ func (api *TraceAPIImpl) doCall(ctx context.Context, dbtx kv.Tx, stateReader sta evm := vm.NewEVM(blockCtx, txCtx, ibs, chainConfig, vmConfig) gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) - execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /*gasBailout*/) + execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailout /*gasBailout*/, engine) } if err != nil { return nil, fmt.Errorf("first run for txIndex %d error: %w", txIndex, err) diff --git a/turbo/jsonrpc/trace_filtering.go b/turbo/jsonrpc/trace_filtering.go index b8ce037e9bc..33aa1358007 100644 --- a/turbo/jsonrpc/trace_filtering.go +++ b/turbo/jsonrpc/trace_filtering.go @@ -614,7 +614,7 @@ func (api *TraceAPIImpl) filterV3(ctx context.Context, dbtx kv.TemporalTx, fromB gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) ibs.SetTxContext(txIndex) var execResult *evmtypes.ExecutionResult - execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailOut) + execResult, err = core.ApplyMessage(evm, msg, gp, true /* refunds */, gasBailOut, engine) if err != nil { if first { first = false @@ -789,11 +789,11 @@ func (api *TraceAPIImpl) callBlock( return nil, nil, err } - msgs := make([]types.Message, len(txs)) + msgs := make([]*types.Message, len(txs)) for i, txn := range txs { isBorStateSyncTxn := txn == borStateSyncTxn var txnHash common.Hash - var msg types.Message + var msg *types.Message var err error if isBorStateSyncTxn { txnHash = borStateSyncTxnHash @@ -804,14 +804,6 @@ func (api *TraceAPIImpl) callBlock( if err != nil { return nil, nil, fmt.Errorf("convert txn into msg: %w", err) } - - // gnosis might have a fee free account here - if msg.FeeCap().IsZero() && engine != nil { - syscall := func(contract common.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, cfg, ibs, header, engine, true /* constCall */) - } - msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) - } } callParams = append(callParams, TraceCallParam{ @@ -916,7 +908,7 @@ func (api *TraceAPIImpl) callTransaction( } var txnHash common.Hash - var msg types.Message + var msg *types.Message if cfg.Bor != nil { txnHash = borStateSyncTxnHash // we use an empty message for bor state sync txn since it gets handled differently @@ -926,14 +918,6 @@ func (api *TraceAPIImpl) callTransaction( if err != nil { return nil, nil, fmt.Errorf("convert txn into msg: %w", err) } - - // gnosis might have a fee free account here - if msg.FeeCap().IsZero() && engine != nil { - syscall := func(contract common.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, cfg, ibs, header, engine, true /* constCall */) - } - msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) - } } callParam := TraceCallParam{ diff --git a/turbo/jsonrpc/tracing.go b/turbo/jsonrpc/tracing.go index b9b53c563c2..88f681f3cdb 100644 --- a/turbo/jsonrpc/tracing.go +++ b/turbo/jsonrpc/tracing.go @@ -162,13 +162,6 @@ func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rp ibs.SetTxContext(txnIndex) msg, _ := txn.AsMessage(*signer, block.BaseFee(), rules) - if msg.FeeCap().IsZero() && engine != nil { - syscall := func(contract common.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, chainConfig, ibs, block.Header(), engine, true /* constCall */) - } - msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) - } - txCtx := evmtypes.TxContext{ TxHash: txnHash, Origin: msg.From(), @@ -201,7 +194,7 @@ func (api *PrivateDebugAPIImpl) traceBlock(ctx context.Context, blockNrOrHash rp usedGas += _usedGas } else { var _usedGas uint64 - _usedGas, err = transactions.TraceTx(ctx, msg, blockCtx, txCtx, block.Hash(), txnIndex, ibs, config, chainConfig, stream, api.evmCallTimeout) + _usedGas, err = transactions.TraceTx(ctx, engine, msg, blockCtx, txCtx, block.Hash(), txnIndex, ibs, config, chainConfig, stream, api.evmCallTimeout) usedGas += _usedGas } if err == nil { @@ -365,7 +358,7 @@ func (api *PrivateDebugAPIImpl) TraceTransaction(ctx context.Context, hash commo } // Trace the transaction and return - _, err = transactions.TraceTx(ctx, msg, blockCtx, txCtx, block.Hash(), txnIndex, ibs, config, chainConfig, stream, api.evmCallTimeout) + _, err = transactions.TraceTx(ctx, engine, msg, blockCtx, txCtx, block.Hash(), txnIndex, ibs, config, chainConfig, stream, api.evmCallTimeout) return err } @@ -434,7 +427,7 @@ func (api *PrivateDebugAPIImpl) TraceCall(ctx context.Context, args ethapi.CallA blockCtx := transactions.NewEVMBlockContext(engine, header, blockNrOrHash.RequireCanonical, dbtx, api._blockReader, chainConfig) txCtx := core.NewEVMTxContext(msg) // Trace the transaction and return - _, err = transactions.TraceTx(ctx, msg, blockCtx, txCtx, hash, 0, ibs, config, chainConfig, stream, api.evmCallTimeout) + _, err = transactions.TraceTx(ctx, engine, msg, blockCtx, txCtx, hash, 0, ibs, config, chainConfig, stream, api.evmCallTimeout) return err } @@ -572,7 +565,7 @@ func (api *PrivateDebugAPIImpl) TraceCallMany(ctx context.Context, bundles []Bun } txCtx = core.NewEVMTxContext(msg) ibs.SetTxContext(txnIndex) - _, err = transactions.TraceTx(ctx, msg, blockCtx, txCtx, block.Hash(), txnIndex, evm.IntraBlockState(), config, chainConfig, stream, api.evmCallTimeout) + _, err = transactions.TraceTx(ctx, api.engine(), msg, blockCtx, txCtx, block.Hash(), txnIndex, evm.IntraBlockState(), config, chainConfig, stream, api.evmCallTimeout) if err != nil { stream.WriteArrayEnd() stream.WriteArrayEnd() diff --git a/turbo/transactions/call.go b/turbo/transactions/call.go index ccfebaa001d..644db44d2bb 100644 --- a/turbo/transactions/call.go +++ b/turbo/transactions/call.go @@ -110,7 +110,7 @@ func DoCall( }() gp := new(core.GasPool).AddGas(msg.Gas()).AddBlobGas(msg.BlobGas()) - result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */) + result, err := core.ApplyMessage(evm, msg, gp, true /* refunds */, false /* gasBailout */, engine) if err != nil { return nil, err } @@ -156,6 +156,7 @@ type ReusableCaller struct { func (r *ReusableCaller) DoCallWithNewGas( ctx context.Context, newGas uint64, + engine consensus.EngineReader, ) (*evmtypes.ExecutionResult, error) { var cancel context.CancelFunc if r.callTimeout > 0 { @@ -183,7 +184,7 @@ func (r *ReusableCaller) DoCallWithNewGas( gp := new(core.GasPool).AddGas(r.message.Gas()).AddBlobGas(r.message.BlobGas()) - result, err := core.ApplyMessage(r.evm, r.message, gp, true /* refunds */, false /* gasBailout */) + result, err := core.ApplyMessage(r.evm, r.message, gp, true /* refunds */, false /* gasBailout */, engine) if err != nil { return nil, err } @@ -243,6 +244,6 @@ func NewReusableCaller( gasCap: gasCap, callTimeout: callTimeout, stateReader: stateReader, - message: &msg, + message: msg, }, nil } diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go index ffd7db24f59..5f5e6394690 100644 --- a/turbo/transactions/tracing.go +++ b/turbo/transactions/tracing.go @@ -83,15 +83,8 @@ func ComputeTxContext(statedb *state.IntraBlockState, engine consensus.EngineRea txn := block.Transactions()[txIndex] statedb.SetTxContext(txIndex) msg, _ := txn.AsMessage(*signer, block.BaseFee(), rules) - if msg.FeeCap().IsZero() && engine != nil { - syscall := func(contract libcommon.Address, data []byte) ([]byte, error) { - return core.SysCallContract(contract, data, cfg, statedb, block.HeaderNoCopy(), engine, true /* constCall */) - } - msg.SetIsFree(engine.IsServiceTransaction(msg.From(), syscall)) - } - - TxContext := core.NewEVMTxContext(msg) - return msg, TxContext, nil + txContext := core.NewEVMTxContext(msg) + return msg, txContext, nil } // TraceTx configures a new tracer according to the provided configuration, and @@ -99,6 +92,7 @@ func ComputeTxContext(statedb *state.IntraBlockState, engine consensus.EngineRea // be tracer dependent. func TraceTx( ctx context.Context, + engine consensus.EngineReader, message core.Message, blockCtx evmtypes.BlockContext, txCtx evmtypes.TxContext, @@ -120,7 +114,7 @@ func TraceTx( execCb := func(evm *vm.EVM, refunds bool) (*evmtypes.ExecutionResult, error) { gp := new(core.GasPool).AddGas(message.Gas()).AddBlobGas(message.BlobGas()) - res, err := core.ApplyMessage(evm, message, gp, refunds, false /* gasBailout */) + res, err := core.ApplyMessage(evm, message, gp, refunds, false /* gasBailout */, engine) if err != nil { return res, err } From 5bc9ca29c5fcc54820086c02d3d1ddbbe63fae90 Mon Sep 17 00:00:00 2001 From: fuyangpengqi <167312867+fuyangpengqi@users.noreply.github.com> Date: Fri, 21 Feb 2025 17:58:54 +0800 Subject: [PATCH 32/42] refactor: use a more straightforward return value (#13846) use a more straightforward return value Signed-off-by: fuyangpengqi <995764973@qq.com> --- cl/phase1/core/state/ssz.go | 2 +- cmd/devnet/blocks/fees.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cl/phase1/core/state/ssz.go b/cl/phase1/core/state/ssz.go index 1ada94bc535..64d163b5b14 100644 --- a/cl/phase1/core/state/ssz.go +++ b/cl/phase1/core/state/ssz.go @@ -28,7 +28,7 @@ func (b *CachingBeaconState) EncodeSSZ(buf []byte) ([]byte, error) { } sz := metrics.NewHistTimer("encode_ssz_beacon_state_size") sz.Observe(float64(len(bts))) - return bts, err + return bts, nil } func (b *CachingBeaconState) DecodeSSZ(buf []byte, version int) error { diff --git a/cmd/devnet/blocks/fees.go b/cmd/devnet/blocks/fees.go index 07ac1812019..d0cbeb8ca8c 100644 --- a/cmd/devnet/blocks/fees.go +++ b/cmd/devnet/blocks/fees.go @@ -31,5 +31,5 @@ func BaseFeeFromBlock(ctx context.Context) (uint64, error) { return 0, fmt.Errorf("failed to get base fee from block: %v\n", err) } - return res.BaseFee.Uint64(), err + return res.BaseFee.Uint64(), nil } From 99a4228f5e4df88db12f5ca13ef7d50451f2d54d Mon Sep 17 00:00:00 2001 From: awskii Date: Fri, 21 Feb 2025 10:19:35 +0000 Subject: [PATCH 33/42] fix use of `erigon snapshots retire --from/to` flags (#13872) From is always set to 0 and ignoring parameter; To is checked in db if not given, but if its given, `forwardProgress` is always 0. --- turbo/app/snapshots_cmd.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/turbo/app/snapshots_cmd.go b/turbo/app/snapshots_cmd.go index 9bf8d1a15f9..243ea2481b6 100644 --- a/turbo/app/snapshots_cmd.go +++ b/turbo/app/snapshots_cmd.go @@ -1383,10 +1383,12 @@ func doRetireCommand(cliCtx *cli.Context, dirs datadir.Dirs) error { if ok { from, to, every = from2, to2, to2-from2 } + } else { + forwardProgress = to } logger.Info("Params", "from", from, "to", to, "every", every) - if err := br.RetireBlocks(ctx, 0, forwardProgress, log.LvlInfo, nil, nil, nil); err != nil { + if err := br.RetireBlocks(ctx, from, forwardProgress, log.LvlInfo, nil, nil, nil); err != nil { return err } From e41969abaacc1d8663a6b8847697ccb3a238e57e Mon Sep 17 00:00:00 2001 From: lupin012 <58134934+lupin012@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:31:03 +0100 Subject: [PATCH 34/42] rpcdaemon: fix in eth_estimateGas/() with stateOverrides field (#13772) In case state Overrides - in NewReusableCaller() it is created the ibs and filled with stateOverrides - in DoCallWithNewGas() the intraBlockState is re-init losing previous initialization (r.intraBlockState = state.New(r.stateReader) ) - the NewReusableCaller() must be called in the execution loop to re-init intra block state with user configuration state changes and not state change from previous run --------- Co-authored-by: alex.sharov --- turbo/jsonrpc/eth_call.go | 12 ++++++------ turbo/transactions/call.go | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/turbo/jsonrpc/eth_call.go b/turbo/jsonrpc/eth_call.go index 7aed0ebab2f..127eb11f1a8 100644 --- a/turbo/jsonrpc/eth_call.go +++ b/turbo/jsonrpc/eth_call.go @@ -273,14 +273,14 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs header := block.HeaderNoCopy() - caller, err := transactions.NewReusableCaller(engine, stateReader, overrides, header, args, api.GasCap, latestNumOrHash, dbtx, api._blockReader, chainConfig, api.evmCallTimeout) - if err != nil { - return 0, err - } - // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, *evmtypes.ExecutionResult, error) { - result, err := caller.DoCallWithNewGas(ctx, gas, engine) + caller, err := transactions.NewReusableCaller(engine, stateReader, overrides, header, args, api.GasCap, latestNumOrHash, dbtx, api._blockReader, chainConfig, api.evmCallTimeout) + if err != nil { + return true, nil, err + } + + result, err := caller.DoCallWithNewGas(ctx, gas, engine, overrides) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { // Special case, raise gas limit diff --git a/turbo/transactions/call.go b/turbo/transactions/call.go index 644db44d2bb..3e19b81c520 100644 --- a/turbo/transactions/call.go +++ b/turbo/transactions/call.go @@ -157,6 +157,7 @@ func (r *ReusableCaller) DoCallWithNewGas( ctx context.Context, newGas uint64, engine consensus.EngineReader, + overrides *ethapi2.StateOverrides, ) (*evmtypes.ExecutionResult, error) { var cancel context.CancelFunc if r.callTimeout > 0 { @@ -173,7 +174,10 @@ func (r *ReusableCaller) DoCallWithNewGas( // reset the EVM so that we can continue to use it with the new context txCtx := core.NewEVMTxContext(r.message) - r.intraBlockState = state.New(r.stateReader) + if overrides == nil { + r.intraBlockState = state.New(r.stateReader) + } + r.evm.Reset(txCtx, r.intraBlockState) timedOut := false From 0e9a509cbfa33e386d02dd8c25fce8402ae0496c Mon Sep 17 00:00:00 2001 From: antonis19 Date: Fri, 21 Feb 2025 12:17:43 +0100 Subject: [PATCH 35/42] Fix mdbx summaries initialization (#13896) - Moved the MDBX metric summaries initialization to `Open()` . - Because the concurrent map is not statically typed, there was an issue when the Put was on key of type `string` but Get was on a key of type `kv.Label`. Now I made both cases have type string. --------- Co-authored-by: antonis19 --- erigon-lib/kv/kv_interface.go | 7 +------ erigon-lib/kv/mdbx/kv_mdbx.go | 7 ++++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/erigon-lib/kv/kv_interface.go b/erigon-lib/kv/kv_interface.go index a8d96daa97a..2b0b4ac0f6a 100644 --- a/erigon-lib/kv/kv_interface.go +++ b/erigon-lib/kv/kv_interface.go @@ -137,11 +137,6 @@ func InitMDBXMGauges() *DBGauges { // initialize summaries for a particular MDBX instance func InitSummaries(dbLabel Label) { - // just in case the global singleton map is not already initialized - // if MDBXSummaries == nil { - // MDBXSummaries = make(map[Label]*DBSummaries) - // } - _, ok := MDBXSummaries.Load(dbLabel) if !ok { dbName := string(dbLabel) @@ -156,7 +151,7 @@ func InitSummaries(dbLabel Label) { } func RecordSummaries(dbLabel Label, latency mdbx.CommitLatency) error { - _summaries, ok := MDBXSummaries.Load(dbLabel) + _summaries, ok := MDBXSummaries.Load(string(dbLabel)) if !ok { return fmt.Errorf("MDBX summaries not initialized yet for db=%s", string(dbLabel)) } diff --git a/erigon-lib/kv/mdbx/kv_mdbx.go b/erigon-lib/kv/mdbx/kv_mdbx.go index 95bab7af69d..a528dc7bdb8 100644 --- a/erigon-lib/kv/mdbx/kv_mdbx.go +++ b/erigon-lib/kv/mdbx/kv_mdbx.go @@ -101,9 +101,6 @@ func New(label kv.Label, log log.Logger) MdbxOpts { if label == kv.ChainDB { opts = opts.RemoveFlags(mdbx.NoReadahead) // enable readahead for chaindata by default. Erigon3 require fast updates and prune. Also it's chaindata is small (doesen GB) } - if opts.metrics { - kv.InitSummaries(label) - } return opts } @@ -189,6 +186,10 @@ func (opts MdbxOpts) Open(ctx context.Context) (kv.RwDB, error) { if dbg.MergeTr() > 0 { opts = opts.WriteMergeThreshold(uint64(dbg.MergeTr() * 8192)) //nolint } + + if opts.metrics { + kv.InitSummaries(opts.label) + } if opts.HasFlag(mdbx.Accede) || opts.HasFlag(mdbx.Readonly) { for retry := 0; ; retry++ { exists, err := dir.FileExist(filepath.Join(opts.path, "mdbx.dat")) From a924d9de0d718787461c08415b2b2bc31d3e1d6e Mon Sep 17 00:00:00 2001 From: Kewei Date: Fri, 21 Feb 2025 21:50:05 +0900 Subject: [PATCH 36/42] Fix failure in block production (#13840) - After Electra, the aggregation bits indicate the bit-maps that appear only in the committee bits, so merge them in a way similar to merge sort - Fix bugs in BitList structure - Remove the problematic SetVersion() function that leads to the empty attestation bug --- cl/aggregation/pool_impl.go | 86 ++++++++---------- cl/aggregation/pool_test.go | 44 ++++----- cl/beacon/handler/block_production.go | 123 +++++++++++++++++++++----- cl/clparams/config.go | 4 + cl/cltypes/beacon_block.go | 19 +--- cl/cltypes/block_production.go | 1 - cl/cltypes/eth1_block.go | 4 - cl/cltypes/solid/attestation.go | 4 +- cl/cltypes/solid/bitlist.go | 72 ++++++++++++--- cl/cltypes/solid/bitlist_test.go | 33 ++++--- cl/cltypes/solid/bitvector.go | 10 +++ 11 files changed, 264 insertions(+), 136 deletions(-) diff --git a/cl/aggregation/pool_impl.go b/cl/aggregation/pool_impl.go index df55512b9fc..55ca8654332 100644 --- a/cl/aggregation/pool_impl.go +++ b/cl/aggregation/pool_impl.go @@ -19,12 +19,12 @@ package aggregation import ( "context" "errors" - "fmt" "sync" "time" "github.com/Giulio2002/bls" "github.com/erigontech/erigon-lib/common" + "github.com/erigontech/erigon-lib/log/v3" "github.com/erigontech/erigon/cl/clparams" "github.com/erigontech/erigon/cl/cltypes/solid" "github.com/erigontech/erigon/cl/phase1/core/state/lru" @@ -44,7 +44,7 @@ type aggregationPoolImpl struct { netConfig *clparams.NetworkConfig ethClock eth_clock.EthereumClock aggregatesLock sync.RWMutex - aggregates map[common.Hash]*solid.Attestation + aggregates map[common.Hash]*solid.Attestation // don't need this anymore after electra upgrade // aggregationInCommittee is a cache for aggregation in committee, which is used after electra upgrade aggregatesInCommittee *lru.CacheWithTTL[keyAggrInCommittee, *solid.Attestation] } @@ -78,68 +78,48 @@ func (p *aggregationPoolImpl) AddAttestation(inAtt *solid.Attestation) error { if err != nil { return err } - p.aggregatesLock.Lock() - defer p.aggregatesLock.Unlock() - att, ok := p.aggregates[hashRoot] - if !ok { - p.aggregates[hashRoot] = inAtt.Copy() - return nil - } - - if utils.IsOverlappingSSZBitlist(att.AggregationBits.Bytes(), inAtt.AggregationBits.Bytes()) { - // the on bit is already set, so ignore - return ErrIsSuperset - } - // merge signature - baseSig := att.Signature - inSig := inAtt.Signature - merged, err := blsAggregate([][]byte{baseSig[:], inSig[:]}) - if err != nil { - return err - } - if len(merged) != 96 { - return errors.New("merged signature is too long") - } - var mergedSig [96]byte - copy(mergedSig[:], merged) - - epoch := p.ethClock.GetEpochAtSlot(att.Data.Slot) + epoch := p.ethClock.GetEpochAtSlot(inAtt.Data.Slot) clversion := p.ethClock.StateVersionByEpoch(epoch) if clversion.BeforeOrEqual(clparams.DenebVersion) { - // merge aggregation bits - mergedBits, err := att.AggregationBits.Union(inAtt.AggregationBits) - if err != nil { - return err + p.aggregatesLock.Lock() + defer p.aggregatesLock.Unlock() + att, ok := p.aggregates[hashRoot] + if !ok { + p.aggregates[hashRoot] = inAtt.Copy() + return nil } - // update attestation - p.aggregates[hashRoot] = &solid.Attestation{ - AggregationBits: mergedBits, - Data: att.Data, - Signature: mergedSig, + + if utils.IsOverlappingSSZBitlist(att.AggregationBits.Bytes(), inAtt.AggregationBits.Bytes()) { + // the on bit is already set, so ignore + return ErrIsSuperset } - } else { - // Electra and after case - aggrBitSize := p.beaconConfig.MaxCommitteesPerSlot * p.beaconConfig.MaxValidatorsPerCommittee - mergedAggrBits, err := att.AggregationBits.Union(inAtt.AggregationBits) + // merge signature + baseSig := att.Signature + inSig := inAtt.Signature + merged, err := blsAggregate([][]byte{baseSig[:], inSig[:]}) if err != nil { return err } - if mergedAggrBits.Cap() != int(aggrBitSize) { - return fmt.Errorf("incorrect aggregation bits size: %d", mergedAggrBits.Cap()) + if len(merged) != 96 { + return errors.New("merged signature is too long") } - mergedCommitteeBits, err := att.CommitteeBits.Union(inAtt.CommitteeBits) + var mergedSig [96]byte + copy(mergedSig[:], merged) + + // merge aggregation bits + mergedBits, err := att.AggregationBits.Merge(inAtt.AggregationBits) if err != nil { return err } + // update attestation p.aggregates[hashRoot] = &solid.Attestation{ - AggregationBits: mergedAggrBits, - CommitteeBits: mergedCommitteeBits, + AggregationBits: mergedBits, Data: att.Data, Signature: mergedSig, } - - // aggregate by committee + } else { + // Electra and after case, aggregate by committee p.aggregateByCommittee(inAtt) } return nil @@ -166,9 +146,15 @@ func (p *aggregationPoolImpl) aggregateByCommittee(inAtt *solid.Attestation) err return nil } - // merge aggregation bits and signature - mergedAggrBits, err := att.AggregationBits.Union(inAtt.AggregationBits) + if utils.IsOverlappingSSZBitlist(att.AggregationBits.Bytes(), inAtt.AggregationBits.Bytes()) { + // the on bit is already set, so ignore + return ErrIsSuperset + } + + // It's fine to directly merge aggregation bits here, because the attestation is from the same committee + mergedAggrBits, err := att.AggregationBits.Merge(inAtt.AggregationBits) if err != nil { + log.Debug("failed to merge aggregation bits", "err", err) return err } merged, err := blsAggregate([][]byte{att.Signature[:], inAtt.Signature[:]}) diff --git a/cl/aggregation/pool_test.go b/cl/aggregation/pool_test.go index 8dd22dbbd13..6b294b8cd93 100644 --- a/cl/aggregation/pool_test.go +++ b/cl/aggregation/pool_test.go @@ -40,22 +40,22 @@ var ( }, } att1_1 = &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00000001, 0, 0, 0}, 2048), + AggregationBits: solid.BitlistFromBytes([]byte{0b00000011}, 2048), Data: attData1, Signature: [96]byte{'a', 'b', 'c', 'd', 'e', 'f'}, } att1_2 = &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00000001, 0, 0, 0}, 2048), + AggregationBits: solid.BitlistFromBytes([]byte{0b00000011}, 2048), Data: attData1, Signature: [96]byte{'d', 'e', 'f', 'g', 'h', 'i'}, } att1_3 = &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00000100, 0, 0, 0}, 2048), + AggregationBits: solid.BitlistFromBytes([]byte{0b00001100}, 2048), Data: attData1, Signature: [96]byte{'g', 'h', 'i', 'j', 'k', 'l'}, } att1_4 = &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00100000, 0, 0, 0}, 2048), + AggregationBits: solid.BitlistFromBytes([]byte{0b01100000}, 2048), Data: attData1, Signature: [96]byte{'m', 'n', 'o', 'p', 'q', 'r'}, } @@ -72,7 +72,7 @@ var ( }, } att2_1 = &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00000001, 0, 0, 0}, 2048), + AggregationBits: solid.BitlistFromBytes([]byte{0b00000001}, 2048), Data: attData2, Signature: [96]byte{'t', 'e', 's', 't', 'i', 'n'}, } @@ -107,21 +107,21 @@ func (t *PoolTestSuite) TearDownTest() { func (t *PoolTestSuite) TestAddAttestationElectra() { cBits1 := solid.NewBitVector(64) - cBits1.SetBitAt(0, true) + cBits1.SetBitAt(10, true) cBits2 := solid.NewBitVector(64) cBits2.SetBitAt(10, true) expectedCommitteeBits := solid.NewBitVector(64) - expectedCommitteeBits.SetBitAt(0, true) + expectedCommitteeBits.SetBitAt(10, true) expectedCommitteeBits.SetBitAt(10, true) att1 := &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00000001, 0, 0, 0}, 2048*64), + AggregationBits: solid.BitlistFromBytes([]byte{0b00000011}, 2048*64), Data: attData1, Signature: [96]byte{'a', 'b', 'c', 'd', 'e', 'f'}, CommitteeBits: cBits1, } att2 := &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00000000, 0b00001000, 0, 0}, 2048*64), + AggregationBits: solid.BitlistFromBytes([]byte{0b00001100}, 2048*64), Data: attData1, Signature: [96]byte{'d', 'e', 'f', 'g', 'h', 'i'}, CommitteeBits: cBits2, @@ -141,11 +141,11 @@ func (t *PoolTestSuite) TestAddAttestationElectra() { }, hashRoot: attData1Root, mockFunc: func() { - t.mockEthClock.EXPECT().GetEpochAtSlot(gomock.Any()).Return(uint64(1)).Times(1) - t.mockEthClock.EXPECT().StateVersionByEpoch(gomock.Any()).Return(clparams.ElectraVersion).Times(1) + t.mockEthClock.EXPECT().GetEpochAtSlot(gomock.Any()).Return(uint64(1)).Times(2) + t.mockEthClock.EXPECT().StateVersionByEpoch(gomock.Any()).Return(clparams.ElectraVersion).Times(2) }, expect: &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b0000001, 0b00001000, 0, 0}, 2048*64), + AggregationBits: solid.BitlistFromBytes([]byte{0b00001101}, 2048*64), Data: attData1, Signature: mockAggrResult, CommitteeBits: expectedCommitteeBits, @@ -162,9 +162,7 @@ func (t *PoolTestSuite) TestAddAttestationElectra() { for i := range tc.atts { pool.AddAttestation(tc.atts[i]) } - att := pool.GetAggregatationByRoot(tc.hashRoot) - //h1, _ := tc.expect.HashSSZ() - //h2, _ := att.HashSSZ() + att := pool.GetAggregatationByRootAndCommittee(tc.hashRoot, 10) t.Equal(tc.expect, att, tc.name) } } @@ -184,7 +182,11 @@ func (t *PoolTestSuite) TestAddAttestation() { att2_1, }, hashRoot: attData1Root, - expect: att1_1, + mockFunc: func() { + t.mockEthClock.EXPECT().GetEpochAtSlot(gomock.Any()).Return(uint64(1)).AnyTimes() + t.mockEthClock.EXPECT().StateVersionByEpoch(gomock.Any()).Return(clparams.DenebVersion).AnyTimes() + }, + expect: att1_1, }, { name: "att1_2 is a super set of att1_1. skip att1_1", @@ -194,7 +196,11 @@ func (t *PoolTestSuite) TestAddAttestation() { att2_1, // none of its business }, hashRoot: attData1Root, - expect: att1_2, + mockFunc: func() { + t.mockEthClock.EXPECT().GetEpochAtSlot(gomock.Any()).Return(uint64(1)).AnyTimes() + t.mockEthClock.EXPECT().StateVersionByEpoch(gomock.Any()).Return(clparams.DenebVersion).AnyTimes() + }, + expect: att1_2, }, { name: "merge att1_2, att1_3, att1_4", @@ -209,7 +215,7 @@ func (t *PoolTestSuite) TestAddAttestation() { t.mockEthClock.EXPECT().StateVersionByEpoch(gomock.Any()).Return(clparams.DenebVersion).AnyTimes() }, expect: &solid.Attestation{ - AggregationBits: solid.BitlistFromBytes([]byte{0b00100101, 0, 0, 0}, 2048), + AggregationBits: solid.BitlistFromBytes([]byte{0b01100101}, 2048), Data: attData1, Signature: mockAggrResult, }, @@ -226,8 +232,6 @@ func (t *PoolTestSuite) TestAddAttestation() { pool.AddAttestation(tc.atts[i]) } att := pool.GetAggregatationByRoot(tc.hashRoot) - //h1, _ := tc.expect.HashSSZ() - //h2, _ := att.HashSSZ() t.Equal(tc.expect, att, tc.name) } } diff --git a/cl/beacon/handler/block_production.go b/cl/beacon/handler/block_production.go index a1a43c00bd8..fbf778b0e0f 100644 --- a/cl/beacon/handler/block_production.go +++ b/cl/beacon/handler/block_production.go @@ -352,6 +352,7 @@ func (a *ApiHandler) GetEthV3ValidatorBlock( "proposerIndex", block.ProposerIndex, "slot", targetSlot, "state_root", block.StateRoot, + "attestations", block.BeaconBody.Attestations.Len(), "execution_value", block.GetExecutionValue().Uint64(), "version", block.Version(), "blinded", block.IsBlinded(), @@ -624,11 +625,11 @@ func (a *ApiHandler) produceBeaconBody( retryTime := 10 * time.Millisecond secsDiff := (targetSlot - baseBlock.Slot) * a.beaconChainCfg.SecondsPerSlot feeRecipient, _ := a.validatorParams.GetFeeRecipient(proposerIndex) - var withdrawals []*types.Withdrawal clWithdrawals, _ := state.ExpectedWithdrawals( baseState, targetSlot/a.beaconChainCfg.SlotsPerEpoch, ) + withdrawals := []*types.Withdrawal{} for _, w := range clWithdrawals { withdrawals = append(withdrawals, &types.Withdrawal{ Index: w.Index, @@ -1067,7 +1068,6 @@ func (a *ApiHandler) parseRequestBeaconBlock( if err := json.NewDecoder(r.Body).Decode(block); err != nil { return nil, err } - block.SignedBlock.Block.SetVersion(version) return block, nil case "application/octet-stream": octect, err := io.ReadAll(r.Body) @@ -1077,7 +1077,6 @@ func (a *ApiHandler) parseRequestBeaconBlock( if err := block.DecodeSSZ(octect, int(version)); err != nil { return nil, err } - block.SignedBlock.Block.SetVersion(version) return block, nil } return nil, errors.New("invalid content type") @@ -1216,12 +1215,7 @@ type attestationCandidate struct { func (a *ApiHandler) findBestAttestationsForBlockProduction( s abstract.BeaconState, ) *solid.ListSSZ[*solid.Attestation] { - currentVersion := s.Version() - aggBitsSize := int(a.beaconChainCfg.MaxValidatorsPerCommittee) - if currentVersion.AfterOrEqual(clparams.ElectraVersion) { - aggBitsSize = int(a.beaconChainCfg.MaxValidatorsPerCommittee * - a.beaconChainCfg.MaxCommitteesPerSlot) - } + stateVersion := s.Version() // Group attestations by their data root hashToAtts := make(map[libcommon.Hash][]*solid.Attestation) for _, candidate := range a.operationsPool.AttestationsPool.Raw() { @@ -1230,7 +1224,7 @@ func (a *ApiHandler) findBestAttestationsForBlockProduction( } attVersion := a.beaconChainCfg.GetCurrentStateVersion(candidate.Data.Slot / a.beaconChainCfg.SlotsPerEpoch) - if currentVersion.AfterOrEqual(clparams.ElectraVersion) && + if stateVersion.AfterOrEqual(clparams.ElectraVersion) && attVersion.Before(clparams.ElectraVersion) { // Because the on chain Attestation container changes, attestations from the prior fork can’t be included // into post-electra blocks. Therefore the first block after the fork may have zero attestations. @@ -1252,7 +1246,8 @@ func (a *ApiHandler) findBestAttestationsForBlockProduction( candidateAggregationBits := candidate.AggregationBits.Bytes() for _, curAtt := range hashToAtts[dataRoot] { currAggregationBitsBytes := curAtt.AggregationBits.Bytes() - if !utils.IsOverlappingSSZBitlist(currAggregationBitsBytes, candidateAggregationBits) { + if stateVersion <= clparams.DenebVersion && + !utils.IsOverlappingSSZBitlist(currAggregationBitsBytes, candidateAggregationBits) { // merge signatures candidateSig := candidate.Signature curSig := curAtt.Signature @@ -1262,24 +1257,38 @@ func (a *ApiHandler) findBestAttestationsForBlockProduction( continue } // merge aggregation bits - mergedAggBits := solid.NewBitList(0, aggBitsSize) - for i := 0; i < len(currAggregationBitsBytes); i++ { - mergedAggBits.Append(currAggregationBitsBytes[i] | candidateAggregationBits[i]) + mergedAggBits, err := curAtt.AggregationBits.Merge(candidate.AggregationBits) + if err != nil { + log.Warn("[Block Production] Cannot merge aggregation bits", "err", err) + continue } var buf [96]byte copy(buf[:], mergeSig) curAtt.Signature = buf curAtt.AggregationBits = mergedAggBits - if attVersion.AfterOrEqual(clparams.ElectraVersion) { - // merge committee_bits for electra - mergedCommitteeBits, err := curAtt.CommitteeBits.Union(candidate.CommitteeBits) - if err != nil { - log.Warn("[Block Production] Cannot merge committee bits", "err", err) - continue - } - curAtt.CommitteeBits = mergedCommitteeBits + mergeAny = true + } + if stateVersion >= clparams.ElectraVersion { + // merge in electra way + mergedAggrBits, ok := a.tryMergeAggregationBits(s, curAtt, candidate) + if !ok { + continue } - + mergedCommitteeBits, err := curAtt.CommitteeBits.Union(candidate.CommitteeBits) + if err != nil { + continue + } + // merge signatures + candidateSig := candidate.Signature + curSig := curAtt.Signature + mergeSig, err := bls.AggregateSignatures([][]byte{candidateSig[:], curSig[:]}) + if err != nil { + log.Warn("[Block Production] Cannot merge signatures", "err", err) + continue + } + curAtt.AggregationBits = mergedAggrBits + curAtt.CommitteeBits = mergedCommitteeBits + copy(curAtt.Signature[:], mergeSig) mergeAny = true } } @@ -1327,6 +1336,74 @@ func (a *ApiHandler) findBestAttestationsForBlockProduction( return ret } +func (a *ApiHandler) tryMergeAggregationBits(state abstract.BeaconState, att1, att2 *solid.Attestation) (*solid.BitList, bool) { + // after electra fork, aggregation_bits contains only the attester bit map of those committee appearing in committee_bits + // ref: https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/validator.md#attestations + slot := att1.Data.Slot + committees1 := att1.CommitteeBits.GetOnIndices() + committees2 := att2.CommitteeBits.GetOnIndices() + bitSlice := solid.NewBitSlice() + index1, index2 := 0, 0 + committeeOffset1, committeeOffset2 := 0, 0 + + // appendBits is a helper func to append the aggregation bits of the committee to the bitSlice + appendBits := func(bitSlice *solid.BitSlice, committeeIndex int, att *solid.Attestation, offset int) (*solid.BitSlice, int) { + members, err := state.GetBeaconCommitee(slot, uint64(committeeIndex)) + if err != nil { + log.Warn("[Block Production] Cannot get committee members", "err", err) + return nil, 0 + } + for i := range members { + bitSlice.AppendBit(att.AggregationBits.GetBitAt(offset + i)) + } + return bitSlice, offset + len(members) + } + + // similar to merge sort + for index1 < len(committees1) || index2 < len(committees2) { + if index1 < len(committees1) && index2 < len(committees2) { + if committees1[index1] < committees2[index2] { + bitSlice, committeeOffset1 = appendBits(bitSlice, committees1[index1], att1, committeeOffset1) + index1++ + } else if committees1[index1] > committees2[index2] { + bitSlice, committeeOffset2 = appendBits(bitSlice, committees2[index2], att2, committeeOffset2) + index2++ + } else { + // check overlapping when the committee is the same + members, err := state.GetBeaconCommitee(slot, uint64(committees1[index1])) + if err != nil { + log.Warn("[Block Production] Cannot get committee members", "err", err) + return nil, false + } + bits1 := att1.AggregationBits + bits2 := att2.AggregationBits + for i := range members { + if bits1.GetBitAt(committeeOffset1+i) && bits2.GetBitAt(committeeOffset2+i) { + // overlapping + return nil, false + } else { + bitSlice.AppendBit(bits1.GetBitAt(committeeOffset1+i) || bits2.GetBitAt(committeeOffset2+i)) + } + } + committeeOffset1 += len(members) + committeeOffset2 += len(members) + index1++ + index2++ + } + } else if index1 < len(committees1) { + bitSlice, committeeOffset1 = appendBits(bitSlice, committees1[index1], att1, committeeOffset1) + index1++ + } else { + bitSlice, committeeOffset2 = appendBits(bitSlice, committees2[index2], att2, committeeOffset2) + index2++ + } + } + + bitSlice.AppendBit(true) // mark the end of the bitlist + mergedAggregationBits := solid.BitlistFromBytes(bitSlice.Bytes(), int(a.beaconChainCfg.MaxCommitteesPerSlot)*int(a.beaconChainCfg.MaxValidatorsPerCommittee)) + return mergedAggregationBits, true +} + // computeAttestationReward computes the reward for a specific attestation. func computeAttestationReward( s abstract.BeaconState, diff --git a/cl/clparams/config.go b/cl/clparams/config.go index 610922497a7..63ab3479b36 100644 --- a/cl/clparams/config.go +++ b/cl/clparams/config.go @@ -533,6 +533,8 @@ type BeaconChainConfig struct { DenebForkEpoch uint64 `yaml:"DENEB_FORK_EPOCH" spec:"true" json:"DENEB_FORK_EPOCH,string"` // DenebForkEpoch is used to represent the assigned fork epoch for Deneb. ElectraForkVersion ConfigForkVersion `yaml:"ELECTRA_FORK_VERSION" spec:"true" json:"ELECTRA_FORK_VERSION"` // ElectraForkVersion is used to represent the fork version for Electra. ElectraForkEpoch uint64 `yaml:"ELECTRA_FORK_EPOCH" spec:"true" json:"ELECTRA_FORK_EPOCH,string"` // ElectraForkEpoch is used to represent the assigned fork epoch for Electra. + FuluForkVersion ConfigForkVersion `yaml:"FULU_FORK_VERSION" spec:"true" json:"FULU_FORK_VERSION"` // FuluForkVersion is used to represent the fork version for Fulu. + FuluForkEpoch uint64 `yaml:"FULU_FORK_EPOCH" spec:"true" json:"FULU_FORK_EPOCH,string"` // FuluForkEpoch is used to represent the assigned fork epoch for Fulu. ForkVersionSchedule map[libcommon.Bytes4]VersionScheduleEntry `json:"-"` // Schedule of fork epochs by version. @@ -823,6 +825,8 @@ var MainnetBeaconConfig BeaconChainConfig = BeaconChainConfig{ DenebForkEpoch: 269568, ElectraForkVersion: 0x05000000, ElectraForkEpoch: math.MaxUint64, + FuluForkVersion: 0x06000000, + FuluForkEpoch: math.MaxUint64, // New values introduced in Altair hard fork 1. // Participation flag indices. diff --git a/cl/cltypes/beacon_block.go b/cl/cltypes/beacon_block.go index fdd24c26632..6b551025a48 100644 --- a/cl/cltypes/beacon_block.go +++ b/cl/cltypes/beacon_block.go @@ -162,10 +162,6 @@ func (b *BeaconBlock) Version() clparams.StateVersion { return b.Body.Version } -func (b *BeaconBlock) SetVersion(version clparams.StateVersion) { - b.Body.SetVersion(version) -} - func (b *BeaconBlock) EncodeSSZ(buf []byte) (dst []byte, err error) { return ssz2.MarshalSSZ(buf, b.Slot, b.ProposerIndex, b.ParentRoot[:], b.StateRoot[:], b.Body) } @@ -247,8 +243,8 @@ func NewBeaconBody(beaconCfg *clparams.BeaconChainConfig, version clparams.State ) if version.AfterOrEqual(clparams.ElectraVersion) { // upgrade to electra - maxAttSlashing = MaxAttesterSlashingsElectra - maxAttestation = MaxAttestationsElectra + maxAttSlashing = int(beaconCfg.MaxAttesterSlashingsElectra) + maxAttestation = int(beaconCfg.MaxAttestationsElectra) executionRequests = NewExecutionRequests(beaconCfg) } @@ -267,15 +263,6 @@ func NewBeaconBody(beaconCfg *clparams.BeaconChainConfig, version clparams.State Version: version, } } -func (b *BeaconBody) SetVersion(version clparams.StateVersion) { - b.Version = version - b.ExecutionPayload.SetVersion(version) - if version.AfterOrEqual(clparams.ElectraVersion) { - b.AttesterSlashings = solid.NewDynamicListSSZ[*AttesterSlashing](MaxAttesterSlashingsElectra) - b.Attestations = solid.NewDynamicListSSZ[*solid.Attestation](MaxAttestationsElectra) - b.ExecutionRequests = NewExecutionRequests(b.beaconCfg) - } -} func (b *BeaconBody) EncodeSSZ(dst []byte) ([]byte, error) { return ssz2.MarshalSSZ(dst, b.getSchema(false)...) @@ -616,7 +603,7 @@ func (b *DenebBeaconBlock) GetParentRoot() libcommon.Hash { } func (b *DenebBeaconBlock) GetBody() GenericBeaconBody { - return b.Block.GetBody() + return b.Block.Body } type DenebSignedBeaconBlock struct { diff --git a/cl/cltypes/block_production.go b/cl/cltypes/block_production.go index 197b5ef9150..d611164be21 100644 --- a/cl/cltypes/block_production.go +++ b/cl/cltypes/block_production.go @@ -69,7 +69,6 @@ func (b *BlindOrExecutionBeaconBlock) ToExecution() *DenebBeaconBlock { } DenebBeaconBlock := NewDenebBeaconBlock(b.Cfg, b.Version()) DenebBeaconBlock.Block = beaconBlock - DenebBeaconBlock.Block.SetVersion(b.Version()) for _, kzgProof := range b.KzgProofs { proof := KZGProof{} copy(proof[:], kzgProof[:]) diff --git a/cl/cltypes/eth1_block.go b/cl/cltypes/eth1_block.go index 5b4997684c1..111405cf4fd 100644 --- a/cl/cltypes/eth1_block.go +++ b/cl/cltypes/eth1_block.go @@ -110,10 +110,6 @@ func NewEth1BlockFromHeaderAndBody(header *types.Header, body *types.RawBody, be return block } -func (b *Eth1Block) SetVersion(version clparams.StateVersion) { - b.version = version -} - func (*Eth1Block) Static() bool { return false } diff --git a/cl/cltypes/solid/attestation.go b/cl/cltypes/solid/attestation.go index 2205143b91a..aa366182f20 100644 --- a/cl/cltypes/solid/attestation.go +++ b/cl/cltypes/solid/attestation.go @@ -173,8 +173,8 @@ func (a *Attestation) UnmarshalJSON(data []byte) error { // data: AttestationData // signature: BLSSignature type SingleAttestation struct { - CommitteeIndex uint64 `json:"committee_index"` - AttesterIndex uint64 `json:"attester_index"` + CommitteeIndex uint64 `json:"committee_index,string"` + AttesterIndex uint64 `json:"attester_index,string"` Data *AttestationData `json:"data"` Signature libcommon.Bytes96 `json:"signature"` } diff --git a/cl/cltypes/solid/bitlist.go b/cl/cltypes/solid/bitlist.go index c6a8cd9a9cf..9c0d9755bd0 100644 --- a/cl/cltypes/solid/bitlist.go +++ b/cl/cltypes/solid/bitlist.go @@ -35,8 +35,6 @@ type BitList struct { c int // current length of the bitlist l int - - hashBuf } // NewBitList creates a brand new BitList, just like when Zordon created the Power Rangers! @@ -128,6 +126,7 @@ func (u *BitList) Set(index int, v byte) { u.u[index] = v } +// removeMsb removes the most significant bit from the list, but doesn't change the length l. func (u *BitList) removeMsb() { for i := len(u.u) - 1; i >= 0; i-- { if u.u[i] != 0 { @@ -138,21 +137,26 @@ func (u *BitList) removeMsb() { } } -func (u *BitList) addMsb() { +// addMsb adds a most significant bit to the list, but doesn't change the length l. +func (u *BitList) addMsb() int { + byteLen := len(u.u) for i := len(u.u) - 1; i >= 0; i-- { if u.u[i] != 0 { msb := bits.Len8(u.u[i]) - if msb == 7 { + if msb == 8 { if i == len(u.u)-1 { u.u = append(u.u, 0) } + byteLen++ u.u[i+1] |= 1 } else { - u.u[i] |= 1 << uint(msb+1) + u.u[i] |= 1 << uint(msb) } break } + byteLen-- } + return byteLen } func (u *BitList) SetOnBit(bitIndex int) { @@ -168,8 +172,8 @@ func (u *BitList) SetOnBit(bitIndex int) { // set the bit u.u[bitIndex/8] |= 1 << uint(bitIndex%8) // set last bit - u.addMsb() - u.l = len(u.u) + byteLen := u.addMsb() + u.l = byteLen } // Length gives us the length of the bitlist, just like a roll call tells us how many Rangers there are. @@ -219,7 +223,15 @@ func (u *BitList) Bits() int { return 0 } // The most significant bit is present in the last byte in the array. - last := u.u[u.l-1] + var last byte + var byteLen int + for i := len(u.u) - 1; i >= 0; i-- { + if u.u[i] != 0 { + last = u.u[i] + byteLen = i + 1 + break + } + } // Determine the position of the most significant bit. msb := bits.Len8(last) @@ -230,7 +242,7 @@ func (u *BitList) Bits() int { // The absolute position of the most significant bit will be the number of // bits in the preceding bytes plus the position of the most significant // bit. Subtract this value by 1 to determine the length of the bitlist. - return 8*(u.l-1) + msb - 1 + return 8*(byteLen-1) + msb - 1 } func (u *BitList) MarshalJSON() ([]byte, error) { @@ -249,7 +261,7 @@ func (u *BitList) UnmarshalJSON(input []byte) error { return u.DecodeSSZ(hex, 0) } -func (u *BitList) Union(other *BitList) (*BitList, error) { +func (u *BitList) Merge(other *BitList) (*BitList, error) { if u.c != other.c { return nil, errors.New("bitlist union: different capacity") } @@ -263,8 +275,48 @@ func (u *BitList) Union(other *BitList) (*BitList, error) { unionFrom = other } // union + unionFrom.removeMsb() + ret.removeMsb() for i := 0; i < unionFrom.l; i++ { ret.u[i] |= unionFrom.u[i] } + unionFrom.addMsb() + byteLen := ret.addMsb() + ret.l = byteLen return ret, nil } + +// BitSlice maintains a slice of bits with underlying byte slice. +// This is just a auxiliary struct for merging BitList. +type BitSlice struct { + container []byte + length int +} + +func NewBitSlice() *BitSlice { + return &BitSlice{ + container: make([]byte, 0), + length: 0, + } +} + +// AppendBit appends one bit to the BitSlice. +func (b *BitSlice) AppendBit(bit bool) { + if b.length%8 == 0 { + b.container = append(b.container, 0) + } + if bit { + b.container[b.length/8] |= 1 << uint(b.length%8) + } + b.length++ +} + +// Bytes returns the underlying byte slice of the BitSlice. +func (b *BitSlice) Bytes() []byte { + return b.container +} + +// Length returns the length of the BitSlice. +func (b *BitSlice) Length() int { + return b.length +} diff --git a/cl/cltypes/solid/bitlist_test.go b/cl/cltypes/solid/bitlist_test.go index 82344b320f0..24f438dea01 100644 --- a/cl/cltypes/solid/bitlist_test.go +++ b/cl/cltypes/solid/bitlist_test.go @@ -125,19 +125,32 @@ func TestBitListCap(t *testing.T) { // Add more tests as needed for other functions in the BitList struct. -func TestBitlistUnion(t *testing.T) { +func TestBitlistMerge(t *testing.T) { require := require.New(t) - b1 := solid.NewBitList(5, 10) - b2 := solid.NewBitList(5, 10) + b1 := solid.BitlistFromBytes([]byte{0b11010000}, 10) + b2 := solid.BitlistFromBytes([]byte{0b00001101}, 10) - b1.Set(0, byte(0b11010000)) - b2.Set(0, byte(0b00001101)) - - merged, err := b1.Union(b2) + merged, err := b1.Merge(b2) require.NoError(err) - require.Equal(5, merged.Length(), "BitList Union did not return the expected length") - require.Equal(byte(0b11011101), merged.Get(0), "BitList Union did not return the expected value") - require.Equal(byte(0b00000000), merged.Get(1), "BitList Union did not return the expected value") + require.Equal(7, merged.Bits(), "BitList Merge did not return the expected number of bits") + require.Equal(1, merged.Length(), "BitList Union did not return the expected length") + require.Equal(byte(0b11010101), merged.Get(0), "BitList Union did not return the expected value") +} + +func TestBitSlice(t *testing.T) { + require := require.New(t) + + bs := solid.NewBitSlice() + + bs.AppendBit(true) + bs.AppendBit(false) + bs.AppendBit(true) + bs.AppendBit(false) + + bytes := bs.Bytes() + + require.Equal([]byte{0b00000101}, bytes, "BitSlice AppendBit did not append the bits correctly") + require.Equal(4, bs.Length(), "BitSlice AppendBit did not increment the length correctly") } diff --git a/cl/cltypes/solid/bitvector.go b/cl/cltypes/solid/bitvector.go index 3f8574c1f0e..6691bf610aa 100644 --- a/cl/cltypes/solid/bitvector.go +++ b/cl/cltypes/solid/bitvector.go @@ -168,3 +168,13 @@ func (b *BitVector) Union(other *BitVector) (*BitVector, error) { } return new, nil } + +func (b *BitVector) IsOverlap(other *BitVector) bool { + // check by bytes + for i := 0; i < len(b.container) && i < len(other.container); i++ { + if b.container[i]&other.container[i] != 0 { + return true + } + } + return false +} From 956b40d79a020246bb1b46f6d2e20b72cd70260c Mon Sep 17 00:00:00 2001 From: Mark Holt <135143369+mh0lt@users.noreply.github.com> Date: Fri, 21 Feb 2025 14:17:30 +0000 Subject: [PATCH 37/42] set ready on file open as well as download complete (#13901) fixes: https://github.com/erigontech/erigon/issues/13897 --- turbo/snapshotsync/snapshots.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/turbo/snapshotsync/snapshots.go b/turbo/snapshotsync/snapshots.go index fd522c5f0e6..7e1a33bf244 100644 --- a/turbo/snapshotsync/snapshots.go +++ b/turbo/snapshotsync/snapshots.go @@ -601,12 +601,11 @@ func (s *RoSnapshots) VisibleBlocksAvailable(t snaptype.Enum) uint64 { } func (s *RoSnapshots) DownloadComplete() { - if !s.SegmentsReady() { - return - } wasReady := s.downloadReady.Swap(true) if !wasReady { - s.ready.set() + if s.SegmentsReady() { + s.ready.set() + } } } @@ -987,7 +986,12 @@ func (s *RoSnapshots) InitSegments(fileNames []string) error { } s.recalcVisibleFiles() - s.segmentsReady.Store(true) + wasReady := s.segmentsReady.Swap(true) + if !wasReady { + if s.downloadReady.Load() { + s.ready.set() + } + } return nil } @@ -1152,7 +1156,12 @@ func (s *RoSnapshots) OpenFolder() error { } s.recalcVisibleFiles() - s.segmentsReady.Store(true) + wasReady := s.segmentsReady.Swap(true) + if !wasReady { + if s.downloadReady.Load() { + s.ready.set() + } + } return nil } From e0b35854f590cb7ae9c095aeab588e92a5a759df Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Fri, 21 Feb 2025 21:56:14 +0100 Subject: [PATCH 38/42] core/vm: return copy of input slice in identity precompile. don't deep copy return data slice upon call completion (#13902) Cherry pick https://github.com/ethereum/go-ethereum/pull/25183. See https://github.com/erigontech/security/issues/4 --- core/vm/contracts.go | 2 +- core/vm/instructions.go | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index b174a599a85..6aefec06778 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -299,7 +299,7 @@ func (c *dataCopy) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas } func (c *dataCopy) Run(in []byte) ([]byte, error) { - return in, nil + return libcommon.CopyBytes(in), nil } // bigModExp implements a native big integer exponential modular operation. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index c61a50a3c55..2a348f7f2d5 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -768,7 +768,6 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } stack.Push(&temp) if err == nil || err == ErrExecutionReverted { - ret = libcommon.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -802,7 +801,6 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } stack.Push(&temp) if err == nil || err == ErrExecutionReverted { - ret = libcommon.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -832,7 +830,6 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } stack.Push(&temp) if err == nil || err == ErrExecutionReverted { - ret = libcommon.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } @@ -862,7 +859,6 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } stack.Push(&temp) if err == nil || err == ErrExecutionReverted { - ret = libcommon.CopyBytes(ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } From 5cabc4d9d097c38c72c8eb4e163a2d2c26baa123 Mon Sep 17 00:00:00 2001 From: milen <94537774+taratorio@users.noreply.github.com> Date: Fri, 21 Feb 2025 23:18:13 +0200 Subject: [PATCH 39/42] stagedsync: dbg option to log receipts on receipts hash mismatch (#13905) ports https://github.com/erigontech/erigon/pull/9340 to E3's `BlockPostValidation` --- core/blockchain.go | 5 ++++- eth/stagedsync/exec3_serial.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index f51d451f256..f4cebd0d6c1 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -376,7 +376,7 @@ func InitializeBlockExecution(engine consensus.Engine, chain consensus.ChainHead return nil } -func BlockPostValidation(gasUsed, blobGasUsed uint64, checkReceipts bool, receipts types.Receipts, h *types.Header, isMining bool) error { +func BlockPostValidation(gasUsed, blobGasUsed uint64, checkReceipts bool, receipts types.Receipts, h *types.Header, isMining bool, txns types.Transactions, chainConfig *chain.Config, logger log.Logger) error { if gasUsed != h.GasUsed { return fmt.Errorf("gas used by execution: %d, in header: %d, headerNum=%d, %x", gasUsed, h.GasUsed, h.Number.Uint64(), h.Hash()) @@ -396,6 +396,9 @@ func BlockPostValidation(gasUsed, blobGasUsed uint64, checkReceipts bool, receip h.ReceiptHash = receiptHash return nil } + if dbg.LogHashMismatchReason() { + logReceipts(receipts, txns, chainConfig, h, logger) + } return fmt.Errorf("receiptHash mismatch: %x != %x, headerNum=%d, %x", receiptHash, h.ReceiptHash, h.Number.Uint64(), h.Hash()) } diff --git a/eth/stagedsync/exec3_serial.go b/eth/stagedsync/exec3_serial.go index 9fc62f3d96c..074810c062b 100644 --- a/eth/stagedsync/exec3_serial.go +++ b/eth/stagedsync/exec3_serial.go @@ -69,7 +69,7 @@ func (se *serialExecutor) execute(ctx context.Context, tasks []*state.TxTask) (c } checkReceipts := !se.cfg.vmConfig.StatelessExec && se.cfg.chainConfig.IsByzantium(txTask.BlockNum) && !se.cfg.vmConfig.NoReceipts && !se.isMining if txTask.BlockNum > 0 && !se.skipPostEvaluation { //Disable check for genesis. Maybe need somehow improve it in future - to satisfy TestExecutionSpec - if err := core.BlockPostValidation(se.usedGas, se.blobGasUsed, checkReceipts, txTask.BlockReceipts, txTask.Header, se.isMining); err != nil { + if err := core.BlockPostValidation(se.usedGas, se.blobGasUsed, checkReceipts, txTask.BlockReceipts, txTask.Header, se.isMining, txTask.Txs, se.cfg.chainConfig, se.logger); err != nil { return fmt.Errorf("%w, txnIdx=%d, %v", consensus.ErrInvalidBlock, txTask.TxIndex, err) //same as in stage_exec.go } } From 9500cc0f4ce2bc0e0ebea738f330881f9a3b0474 Mon Sep 17 00:00:00 2001 From: Somnath Date: Fri, 21 Feb 2025 22:12:46 -0500 Subject: [PATCH 40/42] Basic fix to historical `eth_estimateGas` (#13903) Needed for #13636 (But doesn't optimally achieve the requirements yet) --- turbo/jsonrpc/eth_call.go | 54 ++++++++++++--------------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/turbo/jsonrpc/eth_call.go b/turbo/jsonrpc/eth_call.go index 127eb11f1a8..dd41a714a65 100644 --- a/turbo/jsonrpc/eth_call.go +++ b/turbo/jsonrpc/eth_call.go @@ -150,31 +150,38 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs } defer dbtx.Rollback() + // Use latest block by default + if blockNrOrHash == nil { + blockNrOrHash = &latestNumOrHash + } + chainConfig, err := api.chainConfig(ctx, dbtx) if err != nil { return 0, err } engine := api.engine() - latestCanBlockNumber, latestCanHash, isLatest, err := rpchelper.GetCanonicalBlockNumber(ctx, latestNumOrHash, dbtx, api._blockReader, api.filters) // DoCall cannot be executed on non-canonical blocks + blockNum, blockHash, isLatest, err := rpchelper.GetCanonicalBlockNumber(ctx, *blockNrOrHash, dbtx, api._blockReader, api.filters) // DoCall cannot be executed on non-canonical blocks if err != nil { return 0, err } // try and get the block from the lru cache first then try DB before failing - block := api.tryBlockFromLru(latestCanHash) + block := api.tryBlockFromLru(blockHash) if block == nil { - block, err = api.blockWithSenders(ctx, dbtx, latestCanHash, latestCanBlockNumber) + block, err = api.blockWithSenders(ctx, dbtx, blockHash, blockNum) if err != nil { return 0, err } } + if block == nil { - return 0, errors.New("could not find latest block in cache or db") + 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)) - stateReader, err := rpchelper.CreateStateReaderFromBlockNumber(ctx, dbtx, txNumsReader, latestCanBlockNumber, isLatest, 0, api.stateCache, chainConfig.ChainName) + stateReader, err := rpchelper.CreateStateReaderFromBlockNumber(ctx, dbtx, txNumsReader, blockNum, isLatest, 0, api.stateCache, chainConfig.ChainName) if err != nil { return 0, err } @@ -190,36 +197,12 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs args.From = new(libcommon.Address) } - bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - if blockNrOrHash != nil { - bNrOrHash = *blockNrOrHash - } - // Determine the highest gas limit can be used during the estimation. if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { hi = uint64(*args.Gas) } else { // Retrieve the block to act as the gas ceiling - h, err := headerByNumberOrHash(ctx, dbtx, bNrOrHash, api) - if err != nil { - return 0, err - } - if h == nil { - // if a block number was supplied and there is no header return 0 - if blockNrOrHash != nil { - return 0, nil - } - - // block number not supplied, so we haven't found a pending block, read the latest block instead - h, err = headerByNumberOrHash(ctx, dbtx, latestNumOrHash, api) - if err != nil { - return 0, err - } - if h == nil { - return 0, nil - } - } - hi = h.GasLimit + hi = header.GasLimit } var feeCap *big.Int @@ -271,15 +254,12 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs } gasCap = hi - header := block.HeaderNoCopy() - + caller, err := transactions.NewReusableCaller(engine, stateReader, overrides, header, args, api.GasCap, *blockNrOrHash, dbtx, api._blockReader, chainConfig, api.evmCallTimeout) + if err != nil { + return 0, err + } // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, *evmtypes.ExecutionResult, error) { - caller, err := transactions.NewReusableCaller(engine, stateReader, overrides, header, args, api.GasCap, latestNumOrHash, dbtx, api._blockReader, chainConfig, api.evmCallTimeout) - if err != nil { - return true, nil, err - } - result, err := caller.DoCallWithNewGas(ctx, gas, engine, overrides) if err != nil { if errors.Is(err, core.ErrIntrinsicGas) { From 5cf9ef5d50356605d3c59c5d6e044ca9d2830bd0 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Sat, 22 Feb 2025 17:14:18 +0100 Subject: [PATCH 41/42] qa-tests: add missing dir definition in tip-tracking (#13907) --- .github/workflows/qa-tip-tracking.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/qa-tip-tracking.yml b/.github/workflows/qa-tip-tracking.yml index a166f31d769..d60d2ad1677 100644 --- a/.github/workflows/qa-tip-tracking.yml +++ b/.github/workflows/qa-tip-tracking.yml @@ -14,6 +14,7 @@ jobs: timeout-minutes: 600 env: ERIGON_REFERENCE_DATA_DIR: /opt/erigon-versions/reference-version/datadir + ERIGON_TESTBED_AREA: /opt/erigon-testbed ERIGON_QA_PATH: /home/qarunner/erigon-qa TRACKING_TIME_SECONDS: 14400 # 4 hours TOTAL_TIME_SECONDS: 28800 # 8 hours From 67b5a2f254814f0f37519e193daa37745f7aaaf2 Mon Sep 17 00:00:00 2001 From: Michelangelo Riccobene Date: Sat, 22 Feb 2025 20:24:09 +0100 Subject: [PATCH 42/42] qa-tests: remove RPC test eth_estimateGas/test_14.json (#13909) --- .github/workflows/scripts/run_rpc_tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/scripts/run_rpc_tests.sh b/.github/workflows/scripts/run_rpc_tests.sh index a8558abdd46..081cd3efe65 100755 --- a/.github/workflows/scripts/run_rpc_tests.sh +++ b/.github/workflows/scripts/run_rpc_tests.sh @@ -4,6 +4,8 @@ set +e # Disable exit on error # Array of disabled tests disabled_tests=( + # Failing after the PR https://github.com/erigontech/erigon/pull/13903 - diff is only an error message in the result + eth_estimateGas/test_14.json # Failing after the PR https://github.com/erigontech/erigon/pull/13617 that fixed this incompatibility # issues https://hive.pectra-devnet-5.ethpandaops.io/suite.html?suiteid=1738266984-51ae1a2f376e5de5e9ba68f034f80e32.json&suitename=rpc-compat net_listening/test_1.json