diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 2dd127829f..16d64994d0 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -158,16 +158,12 @@ type Downloader struct { syncStartBlock uint64 // Head snap block when Geth was started syncStartTime time.Time // Time instance when chain sync started syncLogTime time.Time // Time instance when status was last reported - - // Chain ID for downloaders to reference - chainID uint64 } // BlockChain encapsulates functions required to sync a (full or snap) blockchain. type BlockChain interface { // Config returns the chain configuration. // OP-Stack diff, to adjust withdrawal-hash verification. - // Usage of ths in the Downloader is discouraged. Config() *params.ChainConfig // HasHeader verifies a header's presence in the local chain. @@ -229,7 +225,7 @@ type BlockChain interface { } // New creates a new downloader to fetch hashes and blocks from remote peers. -func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func(), chainID uint64) *Downloader { +func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer peerDropFn, success func()) *Downloader { cutoffNumber, cutoffHash := chain.HistoryPruningCutoff() dl := &Downloader{ stateDB: stateDb, @@ -245,7 +241,6 @@ func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, dropPeer SnapSyncer: snap.NewSyncer(stateDb, chain.TrieDB().Scheme()), stateSyncStart: make(chan *stateSync), syncStartBlock: chain.CurrentSnapBlock().Number.Uint64(), - chainID: chainID, } // Create the post-merge skeleton syncer and start the process dl.skeleton = newSkeleton(stateDb, dl.peers, dropPeer, newBeaconBackfiller(dl, success)) @@ -1058,7 +1053,11 @@ func (d *Downloader) commitSnapSyncData(results []*fetchResult, stateSync *state receipts := make([]rlp.RawValue, len(results)) for i, result := range results { blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.body()) - receipts[i] = correctReceiptsRLP(result.Receipts, result.Transactions, blocks[i].NumberU64(), d.chainID) + recs := result.Receipts + if cfg := d.blockchain.Config(); cfg.IsOptimism() && !cfg.IsCanyon(blocks[i].Time()) { + recs = correctReceipts(recs, result.Transactions, blocks[i].NumberU64(), d.blockchain.Config().ChainID) + } + receipts[i] = recs } if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 0c3b0f47e8..c1a31d6e1c 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -75,7 +75,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester { chain: chain, peers: make(map[string]*downloadTesterPeer), } - tester.downloader = New(db, new(event.TypeMux), tester.chain, tester.dropPeer, success, 0) + tester.downloader = New(db, new(event.TypeMux), tester.chain, tester.dropPeer, success) return tester } diff --git a/eth/downloader/receiptreference.go b/eth/downloader/receiptreference.go index 6d313152f1..5ab5bbc40b 100644 --- a/eth/downloader/receiptreference.go +++ b/eth/downloader/receiptreference.go @@ -28,16 +28,16 @@ var ( systemAddress = common.HexToAddress("0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001") receiptReferencePath = "userDepositData" //go:embed userDepositData/*.gob - receiptReference embed.FS - userDepositNoncesAlreadySearched = map[uint64]bool{} - userDepositNoncesReference = map[uint64]userDepositNonces{} + receiptReference embed.FS + userDepositNoncesInitialized = map[uint64]bool{} + userDepositNoncesReference = map[uint64]userDepositNonces{} ) // lazy load the reference data for the requested chain // if this chain data was already requested, returns early func initReceiptReferences(chainID uint64) { // if already loaded, return - if userDepositNoncesAlreadySearched[chainID] { + if userDepositNoncesInitialized[chainID] { return } // look for a file prefixed by the chainID @@ -48,7 +48,7 @@ func initReceiptReferences(chainID uint64) { return } // mark as loaded so we don't try again, even if no files match - userDepositNoncesAlreadySearched[chainID] = true + userDepositNoncesInitialized[chainID] = true for _, file := range files { // skip files which don't match the prefix if !strings.HasPrefix(file.Name(), fPrefix) { @@ -67,8 +67,11 @@ func initReceiptReferences(chainID uint64) { continue } userDepositNoncesReference[udns.ChainID] = udns + log.Info("Receipt Correction: Reference data initialized", "file", fpath, + "chainID", chainID, "count", len(udns.Results), "first", udns.First, "last", udns.Last) return } + log.Info("Receipt Correction: No reference data for chain", "chainID", chainID) } // correctReceipts corrects the deposit nonce in the receipts using the reference data @@ -77,13 +80,19 @@ func initReceiptReferences(chainID uint64) { // This function inspects transaction data for user deposits, and if it is found to be incorrect, it is corrected. // The data used to correct the deposit nonce is stored in the userDepositData directory, // and was generated with the receipt reference tool in the optimism monorepo. -func correctReceipts(receipts types.Receipts, transactions types.Transactions, blockNumber uint64, chainID uint64) types.Receipts { +func correctReceipts(receiptsRLP rlp.RawValue, transactions types.Transactions, blockNumber uint64, chainIDBig *big.Int) rlp.RawValue { + if chainIDBig == nil || !chainIDBig.IsUint64() { + // Known chains that need receipt correction have uint64 chain IDs + return receiptsRLP + } + chainID := chainIDBig.Uint64() initReceiptReferences(chainID) + // if there is no data even after initialization, return the receipts as is depositNoncesForChain, ok := userDepositNoncesReference[chainID] if !ok { log.Trace("Receipt Correction: No data source for chain", "chainID", chainID) - return receipts + return receiptsRLP } // check that the block number being examined is within the range of the reference data @@ -92,35 +101,35 @@ func correctReceipts(receipts types.Receipts, transactions types.Transactions, b "blockNumber", blockNumber, "start", depositNoncesForChain.First, "end", depositNoncesForChain.Last) - return receipts + return receiptsRLP } // get the block nonces blockNonces, ok := depositNoncesForChain.Results[blockNumber] if !ok { log.Trace("Receipt Correction: Block does not contain user deposits", "blockNumber", blockNumber) - return receipts + return receiptsRLP + } + + // Decode RLP receipts to Receipt structs + var receipts []*types.ReceiptForStorage + if err := rlp.DecodeBytes(receiptsRLP, &receipts); err != nil { + log.Warn("Receipt Correction: Failed to decode RLP receipts", "err", err) + return receiptsRLP } - signer := types.LatestSignerForChainID(big.NewInt(int64(chainID))) // iterate through the receipts and transactions to correct the deposit nonce // user deposits should always be at the front of the block, but we will check all transactions to be sure udCount := 0 - for i := 0; i < len(receipts); i++ { - r := receipts[i] + for i, r := range receipts { + tx := transactions[i] // break as soon as a non deposit is found - if r.Type != types.DepositTxType { + if tx.Type() != types.DepositTxType { break } - tx := transactions[i] - from, err := types.Sender(signer, tx) - if err != nil { - log.Warn("Receipt Correction: Failed to determine sender", "err", err) - continue - } // ignore any transactions from the system address - if from != systemAddress { + if from := tx.From(); from != systemAddress { // prevent index out of range (indicates a problem with the reference data or the block data) if udCount >= len(blockNonces) { log.Warn("Receipt Correction: More user deposits in block than included in reference data", "in_reference", len(blockNonces)) @@ -128,12 +137,12 @@ func correctReceipts(receipts types.Receipts, transactions types.Transactions, b } nonce := blockNonces[udCount] udCount++ - log.Trace("Receipt Correction: User Deposit detected", "address", from, "nonce", nonce) + log.Trace("Receipt Correction: User Deposit detected", "from", from, "nonce", nonce) if nonce != *r.DepositNonce { // correct the deposit nonce // warn because this should not happen unless the data was modified by corruption or a malicious peer // by correcting the nonce, the entire block is still valid for use - log.Warn("Receipt Correction: Corrected deposit nonce", "nonce", *r.DepositNonce, "corrected", nonce) + log.Warn("Receipt Correction: Corrected deposit nonce", "from", from, "nonce", *r.DepositNonce, "corrected", nonce) r.DepositNonce = &nonce } } @@ -144,29 +153,12 @@ func correctReceipts(receipts types.Receipts, transactions types.Transactions, b } log.Trace("Receipt Correction: Completed", "blockNumber", blockNumber, "userDeposits", udCount, "receipts", len(receipts), "transactions", len(transactions)) - return receipts -} - -// correctReceiptsRLP corrects the deposit nonce in the receipts using the reference data -// This function works with RLP encoded receipts, decoding them to Receipt structs, -// applying corrections, and re-encoding them back to RLP. -func correctReceiptsRLP(receiptsRLP rlp.RawValue, transactions types.Transactions, blockNumber uint64, chainID uint64) rlp.RawValue { - // Decode RLP receipts to Receipt structs - var receipts types.Receipts - if err := rlp.DecodeBytes(receiptsRLP, &receipts); err != nil { - log.Warn("Receipt Correction: Failed to decode RLP receipts", "err", err) - return receiptsRLP - } - - // Apply corrections using existing correctReceipts function - correctedReceipts := correctReceipts(receipts, transactions, blockNumber, chainID) // Re-encode to RLP - encoded, err := rlp.EncodeToBytes(correctedReceipts) + encoded, err := rlp.EncodeToBytes(receipts) if err != nil { log.Warn("Receipt Correction: Failed to encode corrected receipts to RLP", "err", err) return receiptsRLP } - return encoded } diff --git a/eth/downloader/receiptreference_test.go b/eth/downloader/receiptreference_test.go index e449010f6c..7316c34c30 100644 --- a/eth/downloader/receiptreference_test.go +++ b/eth/downloader/receiptreference_test.go @@ -1,120 +1,41 @@ package downloader import ( + "math/big" + "math/rand" + "slices" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/assert" ) -// makeCorrection is a helper function to create a slice of receipts and a slice of corrected receipts -func makeCorrection(bn uint64, cid uint64, ns []uint64, ty []uint8) (types.Receipts, types.Receipts) { - receipts := make(types.Receipts, len(ns)) - correctedReceipts := make(types.Receipts, len(ns)) - transactions := make(types.Transactions, len(ns)) - for i := range ns { - receipts[i] = &types.Receipt{Type: ty[i], DepositNonce: &ns[i]} - correctedReceipts[i] = &types.Receipt{Type: ty[i], DepositNonce: &ns[i]} - transactions[i] = types.NewTx(&types.DepositTx{}) - } - - correctedReceipts = correctReceipts(correctedReceipts, transactions, bn, cid) - - return receipts, correctedReceipts -} - func TestCorrectReceipts(t *testing.T) { type testcase struct { blockNum uint64 - chainID uint64 nonces []uint64 txTypes []uint8 - validate func(types.Receipts, types.Receipts) + validate func([]*types.ReceiptForStorage, []*types.ReceiptForStorage) } - // Tests use the real reference data, so block numbers and chainIDs are selected for different test cases - testcases := []testcase{ - // Test case 1: No receipts - { - blockNum: 6825767, - chainID: 420, - nonces: []uint64{}, - txTypes: []uint8{}, - validate: func(receipts types.Receipts, correctedReceipts types.Receipts) { - assert.Empty(t, correctedReceipts) - }, - }, - // Test case 2: No deposits - { - blockNum: 6825767, - chainID: 420, - nonces: []uint64{1, 2, 3}, - txTypes: []uint8{1, 1, 1}, - validate: func(receipts types.Receipts, correctedReceipts types.Receipts) { - assert.Equal(t, receipts, correctedReceipts) - }, - }, - // Test case 3: all deposits with no correction - { - blockNum: 8835769, - chainID: 420, - nonces: []uint64{78756, 78757, 78758, 78759, 78760, 78761, 78762, 78763, 78764}, - txTypes: []uint8{126, 126, 126, 126, 126, 126, 126, 126, 126}, - validate: func(receipts types.Receipts, correctedReceipts types.Receipts) { - assert.Equal(t, receipts, correctedReceipts) - }, - }, - // Test case 4: all deposits with a correction - { - blockNum: 8835769, - chainID: 420, - nonces: []uint64{78756, 78757, 78758, 12345, 78760, 78761, 78762, 78763, 78764}, - txTypes: []uint8{126, 126, 126, 126, 126, 126, 126, 126, 126}, - validate: func(receipts types.Receipts, correctedReceipts types.Receipts) { - assert.NotEqual(t, receipts[3], correctedReceipts[3]) - for i := range receipts { - if i != 3 { - assert.Equal(t, receipts[i], correctedReceipts[i]) - } + validateNonceDiff := func(diffIdxs ...int) func(original []*types.ReceiptForStorage, corrected []*types.ReceiptForStorage) { + return func(original, corrected []*types.ReceiptForStorage) { + for i, orig := range original { + corr := corrected[i] + if slices.Contains(diffIdxs, i) { + // expect different deposit nonce for this index + assert.NotEqual(t, orig.DepositNonce, corr.DepositNonce) + // but all other fields that are in RLP storage should equal + assert.Equal(t, orig.CumulativeGasUsed, corr.CumulativeGasUsed) + assert.Equal(t, orig.Status, corr.Status) + assert.Equal(t, orig.Logs, corr.Logs) + } else { + assert.Equal(t, orig, corr) } - }, - }, - // Test case 5: deposits with several corrections and non-deposits - { - blockNum: 8835769, - chainID: 420, - nonces: []uint64{0, 1, 2, 78759, 78760, 78761, 6, 78763, 78764, 9, 10, 11}, - txTypes: []uint8{126, 126, 126, 126, 126, 126, 126, 126, 126, 1, 1, 1}, - validate: func(receipts types.Receipts, correctedReceipts types.Receipts) { - // indexes 0, 1, 2, 6 were modified - // indexes 9, 10, 11 were added too, but they are not user deposits - assert.NotEqual(t, receipts[0], correctedReceipts[0]) - assert.NotEqual(t, receipts[1], correctedReceipts[1]) - assert.NotEqual(t, receipts[2], correctedReceipts[2]) - assert.NotEqual(t, receipts[6], correctedReceipts[6]) - for i := range receipts { - if i != 0 && i != 1 && i != 2 && i != 6 { - assert.Equal(t, receipts[i], correctedReceipts[i]) - } - } - }, - }, - } - - for _, tc := range testcases { - receipts, correctedReceipts := makeCorrection(tc.blockNum, tc.chainID, tc.nonces, tc.txTypes) - tc.validate(receipts, correctedReceipts) - } -} - -func TestCorrectReceiptsRLP(t *testing.T) { - type testcase struct { - blockNum uint64 - chainID uint64 - nonces []uint64 - txTypes []uint8 - validate func(rlp.RawValue, rlp.RawValue) + } + } } // Tests use the real reference data, so block numbers and chainIDs are selected for different test cases @@ -122,92 +43,76 @@ func TestCorrectReceiptsRLP(t *testing.T) { // Test case 1: No receipts { blockNum: 6825767, - chainID: 420, nonces: []uint64{}, txTypes: []uint8{}, - validate: func(originalRLP rlp.RawValue, correctedRLP rlp.RawValue) { - var original, corrected types.Receipts - assert.NoError(t, rlp.DecodeBytes(originalRLP, &original)) - assert.NoError(t, rlp.DecodeBytes(correctedRLP, &corrected)) + validate: func(original []*types.ReceiptForStorage, corrected []*types.ReceiptForStorage) { + assert.Empty(t, original) assert.Empty(t, corrected) }, }, // Test case 2: No deposits { blockNum: 6825767, - chainID: 420, nonces: []uint64{1, 2, 3}, txTypes: []uint8{1, 1, 1}, - validate: func(originalRLP rlp.RawValue, correctedRLP rlp.RawValue) { - var original, corrected types.Receipts - assert.NoError(t, rlp.DecodeBytes(originalRLP, &original)) - assert.NoError(t, rlp.DecodeBytes(correctedRLP, &corrected)) + validate: func(original []*types.ReceiptForStorage, corrected []*types.ReceiptForStorage) { assert.Equal(t, original, corrected) }, }, // Test case 3: all deposits with no correction { blockNum: 8835769, - chainID: 420, nonces: []uint64{78756, 78757, 78758, 78759, 78760, 78761, 78762, 78763, 78764}, txTypes: []uint8{126, 126, 126, 126, 126, 126, 126, 126, 126}, - validate: func(originalRLP rlp.RawValue, correctedRLP rlp.RawValue) { - var original, corrected types.Receipts - assert.NoError(t, rlp.DecodeBytes(originalRLP, &original)) - assert.NoError(t, rlp.DecodeBytes(correctedRLP, &corrected)) + validate: func(original []*types.ReceiptForStorage, corrected []*types.ReceiptForStorage) { assert.Equal(t, original, corrected) }, }, // Test case 4: all deposits with a correction { blockNum: 8835769, - chainID: 420, nonces: []uint64{78756, 78757, 78758, 12345, 78760, 78761, 78762, 78763, 78764}, txTypes: []uint8{126, 126, 126, 126, 126, 126, 126, 126, 126}, - validate: func(originalRLP rlp.RawValue, correctedRLP rlp.RawValue) { - var original, corrected types.Receipts - assert.NoError(t, rlp.DecodeBytes(originalRLP, &original)) - assert.NoError(t, rlp.DecodeBytes(correctedRLP, &corrected)) - assert.NotEqual(t, original[3], corrected[3]) - for i := range original { - if i != 3 { - assert.Equal(t, original[i], corrected[i]) - } - } - }, + validate: validateNonceDiff(3), }, // Test case 5: deposits with several corrections and non-deposits { blockNum: 8835769, - chainID: 420, nonces: []uint64{0, 1, 2, 78759, 78760, 78761, 6, 78763, 78764, 9, 10, 11}, txTypes: []uint8{126, 126, 126, 126, 126, 126, 126, 126, 126, 1, 1, 1}, - validate: func(originalRLP rlp.RawValue, correctedRLP rlp.RawValue) { - var original, corrected types.Receipts - assert.NoError(t, rlp.DecodeBytes(originalRLP, &original)) - assert.NoError(t, rlp.DecodeBytes(correctedRLP, &corrected)) - // indexes 0, 1, 2, 6 were modified - // indexes 9, 10, 11 were added too, but they are not user deposits - assert.NotEqual(t, original[0], corrected[0]) - assert.NotEqual(t, original[1], corrected[1]) - assert.NotEqual(t, original[2], corrected[2]) - assert.NotEqual(t, original[6], corrected[6]) - for i := range original { - if i != 0 && i != 1 && i != 2 && i != 6 { - assert.Equal(t, original[i], corrected[i]) - } - } - }, + // indexes 0, 1, 2, 6 were modified + // indexes 9, 10, 11 were added too, but they are not user deposits + validate: validateNonceDiff(0, 1, 2, 6), }, } + goerliCID := big.NewInt(420) + + rng := rand.New(rand.NewSource(10)) for _, tc := range testcases { // Create original receipts and transactions - receipts := make(types.Receipts, len(tc.nonces)) + receipts := make([]*types.ReceiptForStorage, len(tc.nonces)) transactions := make(types.Transactions, len(tc.nonces)) for i := range tc.nonces { - receipts[i] = &types.Receipt{Type: tc.txTypes[i], DepositNonce: &tc.nonces[i]} - transactions[i] = types.NewTx(&types.DepositTx{}) + rlog := &types.Log{ + Topics: make([]common.Hash, 1), + Data: make([]byte, rng.Intn(16)+1), + } + rng.Read(rlog.Address[:]) + rng.Read(rlog.Topics[0][:]) + rng.Read(rlog.Data) + receipts[i] = &types.ReceiptForStorage{ + CumulativeGasUsed: uint64(rng.Intn(1000)), + Status: 1, + Logs: []*types.Log{rlog}, + DepositNonce: &tc.nonces[i], + } + switch tc.txTypes[i] { + case types.DepositTxType: + transactions[i] = types.NewTx(&types.DepositTx{}) + case types.AccessListTxType: + transactions[i] = types.NewTx(&types.AccessListTx{}) + } } // Encode original receipts to RLP @@ -215,9 +120,15 @@ func TestCorrectReceiptsRLP(t *testing.T) { assert.NoError(t, err) // Call correctReceiptsRLP - correctedRLP := correctReceiptsRLP(originalRLP, transactions, tc.blockNum, tc.chainID) + correctedRLP := correctReceipts(originalRLP, transactions, tc.blockNum, goerliCID) // Validate the results - tc.validate(originalRLP, correctedRLP) + var corrected []*types.ReceiptForStorage + assert.NoError(t, rlp.DecodeBytes(correctedRLP, &corrected)) + for _, r := range corrected { + // Nuke Bloom field which isn't available in the original + r.Bloom = types.Bloom{} + } + tc.validate(receipts, corrected) } } diff --git a/eth/handler.go b/eth/handler.go index 9640a189f2..7b8116351c 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -195,14 +195,8 @@ func newHandler(config *handlerConfig) (*handler, error) { if h.snapSync.Load() && (config.Chain.Snapshots() == nil && config.Chain.TrieDB().Scheme() == rawdb.HashScheme) { return nil, errors.New("snap sync not supported with snapshots disabled") } - // if the chainID is set, pass it to the downloader for use in sync - // this might not be set in tests - var chainID uint64 - if cid := h.chain.Config().ChainID; cid != nil { - chainID = cid.Uint64() - } // Construct the downloader (long sync) - h.downloader = downloader.New(config.Database, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures, chainID) + h.downloader = downloader.New(config.Database, h.eventMux, h.chain, h.removePeer, h.enableSyncedFeatures) fetchTx := func(peer string, hashes []common.Hash) error { p := h.peers.peer(peer)