mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
feat: L1 msg tx (#610)
* update consensus/errors.go * update core/types/receipt.go * update core/genesis.go * update core/blockchain.go * update core/types/block.go * update eth/handler.go * update internal/ethapi/api.go * update core/block_validator.go * update internal/jsre/deps/web3.js * add core/rawdb/accessors_skipped_txs.go & core/rawdb/accessors_skipped_txs_test.go * update core/rawdb/schema.go * update core/state_transition.go part-1 * update accounts/abi/bind/backends/simulated.go & interfaces.go * update core/types/transaction.go * update core/types/transaction_marshalling.go part-1 * update core/types/transaction_marshalling.go part-2 * update core/state_transition.go part-2 * update core/types/transaction_signing.go
This commit is contained in:
parent
35b8da75a0
commit
adfec9a0a3
18 changed files with 790 additions and 5 deletions
|
|
@ -695,6 +695,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
|
||||||
Data: call.Data,
|
Data: call.Data,
|
||||||
AccessList: call.AccessList,
|
AccessList: call.AccessList,
|
||||||
SkipAccountChecks: true,
|
SkipAccountChecks: true,
|
||||||
|
IsL1MessageTx: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new environment which holds all relevant information
|
// Create a new environment which holds all relevant information
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,18 @@ var (
|
||||||
|
|
||||||
// ErrInvalidTxCount is returned if a block contains too many transactions.
|
// ErrInvalidTxCount is returned if a block contains too many transactions.
|
||||||
ErrInvalidTxCount = errors.New("invalid transaction count")
|
ErrInvalidTxCount = errors.New("invalid transaction count")
|
||||||
|
|
||||||
|
// ErrMissingL1MessageData is returned if a block contains L1 messages that the
|
||||||
|
// node has not synced yet. In this case we insert the block into the future
|
||||||
|
// queue and process it again later.
|
||||||
|
ErrMissingL1MessageData = errors.New("unknown L1 message data")
|
||||||
|
|
||||||
|
// ErrInvalidL1MessageOrder is returned if a block contains L1 messages in the wrong
|
||||||
|
// order. Possible scenarios are: (1) L1 messages do not follow their QueueIndex order,
|
||||||
|
// (2) L1 messages are not included in a contiguous block at the front of the block.
|
||||||
|
ErrInvalidL1MessageOrder = errors.New("invalid L1 message order")
|
||||||
|
|
||||||
|
// ErrUnknownL1Message is returned if a block contains an L1 message that does not
|
||||||
|
// match the corresponding message in the node's local database.
|
||||||
|
ErrUnknownL1Message = errors.New("unknown L1 message")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
@ -124,6 +126,97 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
}
|
}
|
||||||
return consensus.ErrPrunedAncestor
|
return consensus.ErrPrunedAncestor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := v.ValidateL1Messages(block); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateL1Messages validates L1 messages contained in a block.
|
||||||
|
// We check the following conditions:
|
||||||
|
// - L1 messages are in a contiguous section at the front of the block.
|
||||||
|
// - The first L1 message's QueueIndex is right after the last L1 message included in the chain.
|
||||||
|
// - L1 messages follow the QueueIndex order.
|
||||||
|
// - The L1 messages included in the block match the node's view of the L1 ledger.
|
||||||
|
func (v *BlockValidator) ValidateL1Messages(block *types.Block) error {
|
||||||
|
// skip DB read if the block contains no L1 messages
|
||||||
|
if !block.ContainsL1Messages() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHash := block.Hash()
|
||||||
|
|
||||||
|
if v.config.Scroll.L1Config == nil {
|
||||||
|
// TODO: should we allow follower nodes to skip L1 message verification?
|
||||||
|
panic("Running on L1Message-enabled network but no l1Config was provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
nextQueueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(v.bc.db, block.ParentHash())
|
||||||
|
if nextQueueIndex == nil {
|
||||||
|
// we'll reprocess this block at a later time
|
||||||
|
return consensus.ErrMissingL1MessageData
|
||||||
|
}
|
||||||
|
queueIndex := *nextQueueIndex
|
||||||
|
|
||||||
|
L1SectionOver := false
|
||||||
|
it := rawdb.IterateL1MessagesFrom(v.bc.db, queueIndex)
|
||||||
|
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
if !tx.IsL1MessageTx() {
|
||||||
|
L1SectionOver = true
|
||||||
|
continue // we do not verify L2 transactions here
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that L1 messages are before L2 transactions
|
||||||
|
if L1SectionOver {
|
||||||
|
return consensus.ErrInvalidL1MessageOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
// queue index cannot decrease
|
||||||
|
txQueueIndex := tx.AsL1MessageTx().QueueIndex
|
||||||
|
|
||||||
|
if txQueueIndex < queueIndex {
|
||||||
|
return consensus.ErrInvalidL1MessageOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
// skipped messages
|
||||||
|
// TODO: consider verifying that skipped messages overflow
|
||||||
|
for index := queueIndex; index < txQueueIndex; index++ {
|
||||||
|
if exists := it.Next(); !exists {
|
||||||
|
// the message in this block is not available in our local db.
|
||||||
|
// we'll reprocess this block at a later time.
|
||||||
|
return consensus.ErrMissingL1MessageData
|
||||||
|
}
|
||||||
|
|
||||||
|
l1msg := it.L1Message()
|
||||||
|
skippedTx := types.NewTx(&l1msg)
|
||||||
|
log.Debug("Skipped L1 message", "queueIndex", index, "tx", skippedTx.Hash().String(), "block", blockHash.String())
|
||||||
|
rawdb.WriteSkippedTransaction(v.bc.db, skippedTx, nil, "unknown", block.NumberU64(), &blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
queueIndex = txQueueIndex + 1
|
||||||
|
|
||||||
|
if exists := it.Next(); !exists {
|
||||||
|
// the message in this block is not available in our local db.
|
||||||
|
// we'll reprocess this block at a later time.
|
||||||
|
return consensus.ErrMissingL1MessageData
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the L1 message in the block is the same that we collected from L1
|
||||||
|
msg := it.L1Message()
|
||||||
|
expectedHash := types.NewTx(&msg).Hash()
|
||||||
|
|
||||||
|
if tx.Hash() != expectedHash {
|
||||||
|
return consensus.ErrUnknownL1Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider adding a rule to enforce L1Config.NumL1MessagesPerBlock.
|
||||||
|
// If there are L1 messages available, sequencer nodes should include them.
|
||||||
|
// However, this is hard to enforce as different nodes might have different views of L1.
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -319,6 +319,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
||||||
return nil, ErrNoGenesis
|
return nil, ErrNoGenesis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize L1 message index for genesis block
|
||||||
|
rawdb.WriteFirstQueueIndexNotInL2Block(db, bc.genesisBlock.Hash(), 0)
|
||||||
|
|
||||||
bc.currentBlock.Store(nil)
|
bc.currentBlock.Store(nil)
|
||||||
bc.currentSnapBlock.Store(nil)
|
bc.currentSnapBlock.Store(nil)
|
||||||
bc.currentFinalBlock.Store(nil)
|
bc.currentFinalBlock.Store(nil)
|
||||||
|
|
@ -866,6 +869,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
||||||
batch := bc.db.NewBatch()
|
batch := bc.db.NewBatch()
|
||||||
rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
|
rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
|
||||||
rawdb.WriteBlock(batch, genesis)
|
rawdb.WriteBlock(batch, genesis)
|
||||||
|
rawdb.WriteFirstQueueIndexNotInL2Block(batch, genesis.Hash(), 0)
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write genesis block", "err", err)
|
log.Crit("Failed to write genesis block", "err", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1364,6 +1368,36 @@ func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (e
|
||||||
batch := bc.db.NewBatch()
|
batch := bc.db.NewBatch()
|
||||||
rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td)
|
rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td)
|
||||||
rawdb.WriteBlock(batch, block)
|
rawdb.WriteBlock(batch, block)
|
||||||
|
|
||||||
|
queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(bc.db, block.ParentHash())
|
||||||
|
// note: we can insert blocks with header-only ancestors here,
|
||||||
|
// so queueIndex might not yet be available in DB.
|
||||||
|
if queueIndex != nil {
|
||||||
|
numProcessed := uint64(block.NumL1MessagesProcessed(*queueIndex))
|
||||||
|
// do not overwrite the index written by the miner worker
|
||||||
|
if index := rawdb.ReadFirstQueueIndexNotInL2Block(bc.db, block.Hash()); index == nil {
|
||||||
|
newIndex := *queueIndex + numProcessed
|
||||||
|
log.Trace(
|
||||||
|
"Blockchain.writeBlockWithoutState WriteFirstQueueIndexNotInL2Block",
|
||||||
|
"number", block.Number(),
|
||||||
|
"hash", block.Hash().String(),
|
||||||
|
"queueIndex", *queueIndex,
|
||||||
|
"numProcessed", numProcessed,
|
||||||
|
"newIndex", newIndex,
|
||||||
|
)
|
||||||
|
rawdb.WriteFirstQueueIndexNotInL2Block(batch, block.Hash(), newIndex)
|
||||||
|
} else {
|
||||||
|
log.Trace(
|
||||||
|
"Blockchain.writeBlockWithoutState WriteFirstQueueIndexNotInL2Block: not overwriting existing index",
|
||||||
|
"number", block.Number(),
|
||||||
|
"hash", block.Hash().String(),
|
||||||
|
"queueIndex", *queueIndex,
|
||||||
|
"numProcessed", numProcessed,
|
||||||
|
"index", *index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Crit("Failed to write block into disk", "err", err)
|
log.Crit("Failed to write block into disk", "err", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1403,6 +1437,37 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||||
rawdb.WriteBlock(blockBatch, block)
|
rawdb.WriteBlock(blockBatch, block)
|
||||||
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
||||||
rawdb.WritePreimages(blockBatch, state.Preimages())
|
rawdb.WritePreimages(blockBatch, state.Preimages())
|
||||||
|
|
||||||
|
queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(bc.db, block.ParentHash())
|
||||||
|
if queueIndex == nil {
|
||||||
|
// We expect that we only insert contiguous chain segments,
|
||||||
|
// so the parent will always be inserted first.
|
||||||
|
log.Crit("Queue index in DB is nil", "parent", block.ParentHash(), "hash", block.Hash())
|
||||||
|
}
|
||||||
|
numProcessed := uint64(block.NumL1MessagesProcessed(*queueIndex))
|
||||||
|
// do not overwrite the index written by the miner worker
|
||||||
|
if index := rawdb.ReadFirstQueueIndexNotInL2Block(bc.db, block.Hash()); index == nil {
|
||||||
|
newIndex := *queueIndex + numProcessed
|
||||||
|
log.Trace(
|
||||||
|
"Blockchain.writeBlockWithState WriteFirstQueueIndexNotInL2Block",
|
||||||
|
"number", block.Number(),
|
||||||
|
"hash", block.Hash().String(),
|
||||||
|
"queueIndex", *queueIndex,
|
||||||
|
"numProcessed", numProcessed,
|
||||||
|
"newIndex", newIndex,
|
||||||
|
)
|
||||||
|
rawdb.WriteFirstQueueIndexNotInL2Block(blockBatch, block.Hash(), newIndex)
|
||||||
|
} else {
|
||||||
|
log.Trace(
|
||||||
|
"Blockchain.writeBlockWithState WriteFirstQueueIndexNotInL2Block: not overwriting existing index",
|
||||||
|
"number", block.Number(),
|
||||||
|
"hash", block.Hash().String(),
|
||||||
|
"queueIndex", *queueIndex,
|
||||||
|
"numProcessed", numProcessed,
|
||||||
|
"index", *index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if err := blockBatch.Write(); err != nil {
|
if err := blockBatch.Write(); err != nil {
|
||||||
log.Crit("Failed to write block into disk", "err", err)
|
log.Crit("Failed to write block into disk", "err", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1686,7 +1751,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
|
||||||
return it.index, err
|
return it.index, err
|
||||||
}
|
}
|
||||||
// First block is future, shove it (and all children) to the future queue (unknown ancestor)
|
// First block is future, shove it (and all children) to the future queue (unknown ancestor)
|
||||||
case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
|
case errors.Is(err, consensus.ErrFutureBlock) || errors.Is(err, consensus.ErrMissingL1MessageData) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
|
||||||
for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) {
|
for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) {
|
||||||
log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash())
|
log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash())
|
||||||
if err := bc.addFutureBlock(block); err != nil {
|
if err := bc.addFutureBlock(block); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,7 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block
|
||||||
rawdb.WriteHeadFastBlockHash(db, block.Hash())
|
rawdb.WriteHeadFastBlockHash(db, block.Hash())
|
||||||
rawdb.WriteHeadHeaderHash(db, block.Hash())
|
rawdb.WriteHeadHeaderHash(db, block.Hash())
|
||||||
rawdb.WriteChainConfig(db, block.Hash(), config)
|
rawdb.WriteChainConfig(db, block.Hash(), config)
|
||||||
|
rawdb.WriteFirstQueueIndexNotInL2Block(db, block.Hash(), 0)
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
255
core/rawdb/accessors_skipped_txs.go
Normal file
255
core/rawdb/accessors_skipped_txs.go
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
package rawdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mutex used to avoid concurrent updates of NumSkippedTransactions
|
||||||
|
var mu sync.Mutex
|
||||||
|
|
||||||
|
// writeNumSkippedTransactions writes the number of skipped transactions to the database.
|
||||||
|
func writeNumSkippedTransactions(db ethdb.KeyValueWriter, numSkipped uint64) {
|
||||||
|
value := big.NewInt(0).SetUint64(numSkipped).Bytes()
|
||||||
|
|
||||||
|
if err := db.Put(numSkippedTransactionsKey, value); err != nil {
|
||||||
|
log.Crit("Failed to update the number of skipped transactions", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadNumSkippedTransactions retrieves the number of skipped transactions.
|
||||||
|
func ReadNumSkippedTransactions(db ethdb.Reader) uint64 {
|
||||||
|
data, err := db.Get(numSkippedTransactionsKey)
|
||||||
|
if err != nil && isNotFoundErr(err) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read number of skipped transactions from database", "err", err)
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
number := new(big.Int).SetBytes(data)
|
||||||
|
if !number.IsUint64() {
|
||||||
|
log.Crit("Unexpected number of skipped transactions in database", "number", number)
|
||||||
|
}
|
||||||
|
return number.Uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkippedTransaction stores the transaction object, along with the skip reason and block context.
|
||||||
|
type SkippedTransaction struct {
|
||||||
|
// Tx is the skipped transaction.
|
||||||
|
// We store the tx itself because otherwise geth will discard it after skipping.
|
||||||
|
Tx *types.Transaction
|
||||||
|
|
||||||
|
// Reason is the skip reason.
|
||||||
|
Reason string
|
||||||
|
|
||||||
|
// BlockNumber is the number of the block in which this transaction was skipped.
|
||||||
|
BlockNumber uint64
|
||||||
|
|
||||||
|
// BlockHash is the hash of the block in which this transaction was skipped or nil.
|
||||||
|
BlockHash *common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkippedTransactionV2 stores the SkippedTransaction object along with serialized traces.
|
||||||
|
type SkippedTransactionV2 struct {
|
||||||
|
// Tx is the skipped transaction.
|
||||||
|
// We store the tx itself otherwise geth will discard it after skipping.
|
||||||
|
Tx *types.Transaction
|
||||||
|
|
||||||
|
// Traces is the serialized wrapped traces of the skipped transaction.
|
||||||
|
// We only store it when `MinerStoreSkippedTxTracesFlag` is enabled, so it might be empty.
|
||||||
|
// Note that we do not directly utilize `*types.BlockTrace` due to the fact that
|
||||||
|
// types.BlockTrace.StorageTrace.Proofs is of type `map[string][]hexutil.Bytes`, which is not RLP-serializable.
|
||||||
|
TracesBytes []byte
|
||||||
|
|
||||||
|
// Reason is the skip reason.
|
||||||
|
Reason string
|
||||||
|
|
||||||
|
// BlockNumber is the number of the block in which this transaction was skipped.
|
||||||
|
BlockNumber uint64
|
||||||
|
|
||||||
|
// BlockHash is the hash of the block in which this transaction was skipped or nil.
|
||||||
|
BlockHash *common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeSkippedTransaction writes a skipped transaction to the database.
|
||||||
|
func writeSkippedTransaction(db ethdb.KeyValueWriter, tx *types.Transaction, traces *types.BlockTrace, reason string, blockNumber uint64, blockHash *common.Hash) {
|
||||||
|
var err error
|
||||||
|
// workaround: RLP decoding fails if this is nil
|
||||||
|
if blockHash == nil {
|
||||||
|
blockHash = &common.Hash{}
|
||||||
|
}
|
||||||
|
stx := SkippedTransactionV2{Tx: tx, Reason: reason, BlockNumber: blockNumber, BlockHash: blockHash}
|
||||||
|
if traces != nil {
|
||||||
|
if stx.TracesBytes, err = json.Marshal(traces); err != nil {
|
||||||
|
log.Crit("Failed to json marshal skipped transaction", "hash", tx.Hash().String(), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes, err := rlp.EncodeToBytes(stx)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to RLP encode skipped transaction", "hash", tx.Hash().String(), "err", err)
|
||||||
|
}
|
||||||
|
if err := db.Put(SkippedTransactionKey(tx.Hash()), bytes); err != nil {
|
||||||
|
log.Crit("Failed to store skipped transaction", "hash", tx.Hash().String(), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeSkippedTransactionV1 is the old version of writeSkippedTransaction, we keep it for testing compatibility purpose.
|
||||||
|
func writeSkippedTransactionV1(db ethdb.KeyValueWriter, tx *types.Transaction, reason string, blockNumber uint64, blockHash *common.Hash) {
|
||||||
|
// workaround: RLP decoding fails if this is nil
|
||||||
|
if blockHash == nil {
|
||||||
|
blockHash = &common.Hash{}
|
||||||
|
}
|
||||||
|
stx := SkippedTransaction{Tx: tx, Reason: reason, BlockNumber: blockNumber, BlockHash: blockHash}
|
||||||
|
bytes, err := rlp.EncodeToBytes(stx)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to RLP encode skipped transaction", "hash", tx.Hash().String(), "err", err)
|
||||||
|
}
|
||||||
|
if err := db.Put(SkippedTransactionKey(tx.Hash()), bytes); err != nil {
|
||||||
|
log.Crit("Failed to store skipped transaction", "hash", tx.Hash().String(), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readSkippedTransactionRLP retrieves a skipped transaction in its raw RLP database encoding.
|
||||||
|
func readSkippedTransactionRLP(db ethdb.Reader, txHash common.Hash) rlp.RawValue {
|
||||||
|
data, err := db.Get(SkippedTransactionKey(txHash))
|
||||||
|
if err != nil && isNotFoundErr(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to load skipped transaction", "hash", txHash.String(), "err", err)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSkippedTransaction retrieves a skipped transaction by its hash, along with its skipped reason.
|
||||||
|
func ReadSkippedTransaction(db ethdb.Reader, txHash common.Hash) *SkippedTransactionV2 {
|
||||||
|
data := readSkippedTransactionRLP(db, txHash)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var stxV2 SkippedTransactionV2
|
||||||
|
var stx SkippedTransaction
|
||||||
|
if err := rlp.Decode(bytes.NewReader(data), &stxV2); err != nil {
|
||||||
|
if err := rlp.Decode(bytes.NewReader(data), &stx); err != nil {
|
||||||
|
log.Crit("Invalid skipped transaction RLP", "hash", txHash.String(), "data", data, "err", err)
|
||||||
|
}
|
||||||
|
stxV2.Tx = stx.Tx
|
||||||
|
stxV2.Reason = stx.Reason
|
||||||
|
stxV2.BlockNumber = stx.BlockNumber
|
||||||
|
stxV2.BlockHash = stx.BlockHash
|
||||||
|
}
|
||||||
|
|
||||||
|
if stxV2.BlockHash != nil && *stxV2.BlockHash == (common.Hash{}) {
|
||||||
|
stxV2.BlockHash = nil
|
||||||
|
}
|
||||||
|
return &stxV2
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeSkippedTransactionHash writes the hash of a skipped transaction to the database.
|
||||||
|
func writeSkippedTransactionHash(db ethdb.KeyValueWriter, index uint64, txHash common.Hash) {
|
||||||
|
if err := db.Put(SkippedTransactionHashKey(index), txHash[:]); err != nil {
|
||||||
|
log.Crit("Failed to store skipped transaction hash", "index", index, "hash", txHash.String(), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSkippedTransactionHash retrieves the hash of a skipped transaction by its index.
|
||||||
|
func ReadSkippedTransactionHash(db ethdb.Reader, index uint64) *common.Hash {
|
||||||
|
data, err := db.Get(SkippedTransactionHashKey(index))
|
||||||
|
if err != nil && isNotFoundErr(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to load skipped transaction hash", "index", index, "err", err)
|
||||||
|
}
|
||||||
|
hash := common.BytesToHash(data)
|
||||||
|
return &hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteSkippedTransaction writes a skipped transaction to the database and also updates the count and lookup index.
|
||||||
|
// Note: The lookup index and count will include duplicates if there are chain reorgs.
|
||||||
|
func WriteSkippedTransaction(db ethdb.Database, tx *types.Transaction, traces *types.BlockTrace, reason string, blockNumber uint64, blockHash *common.Hash) {
|
||||||
|
// this method is not accessed concurrently, but just to be sure...
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
index := ReadNumSkippedTransactions(db)
|
||||||
|
|
||||||
|
// update in a batch
|
||||||
|
batch := db.NewBatch()
|
||||||
|
writeSkippedTransaction(batch, tx, traces, reason, blockNumber, blockHash)
|
||||||
|
writeSkippedTransactionHash(batch, index, tx.Hash())
|
||||||
|
writeNumSkippedTransactions(batch, index+1)
|
||||||
|
|
||||||
|
// write to DB
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
log.Crit("Failed to store skipped transaction", "hash", tx.Hash().String(), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkippedTransactionIterator is a wrapper around ethdb.Iterator that
|
||||||
|
// allows us to iterate over skipped transaction hashes in the database.
|
||||||
|
// It implements an interface similar to ethdb.Iterator.
|
||||||
|
type SkippedTransactionIterator struct {
|
||||||
|
inner ethdb.Iterator
|
||||||
|
db ethdb.Reader
|
||||||
|
keyLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
// IterateSkippedTransactionsFrom creates a SkippedTransactionIterator that iterates
|
||||||
|
// over all skipped transaction hashes in the database starting at the provided index.
|
||||||
|
func IterateSkippedTransactionsFrom(db ethdb.Database, index uint64) SkippedTransactionIterator {
|
||||||
|
start := encodeBigEndian(index)
|
||||||
|
it := db.NewIterator(skippedTransactionHashPrefix, start)
|
||||||
|
keyLength := len(skippedTransactionHashPrefix) + 8
|
||||||
|
|
||||||
|
return SkippedTransactionIterator{
|
||||||
|
inner: it,
|
||||||
|
db: db,
|
||||||
|
keyLength: keyLength,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next moves the iterator to the next key/value pair.
|
||||||
|
// It returns false when the iterator is exhausted.
|
||||||
|
// TODO: Consider reading items in batches.
|
||||||
|
func (it *SkippedTransactionIterator) Next() bool {
|
||||||
|
for it.inner.Next() {
|
||||||
|
key := it.inner.Key()
|
||||||
|
if len(key) == it.keyLength {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the index of the current skipped transaction hash.
|
||||||
|
func (it *SkippedTransactionIterator) Index() uint64 {
|
||||||
|
key := it.inner.Key()
|
||||||
|
raw := key[len(skippedTransactionHashPrefix) : len(skippedTransactionHashPrefix)+8]
|
||||||
|
index := binary.BigEndian.Uint64(raw)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionHash returns the current skipped transaction hash.
|
||||||
|
func (it *SkippedTransactionIterator) TransactionHash() common.Hash {
|
||||||
|
data := it.inner.Value()
|
||||||
|
return common.BytesToHash(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the associated resources.
|
||||||
|
func (it *SkippedTransactionIterator) Release() {
|
||||||
|
it.inner.Release()
|
||||||
|
}
|
||||||
144
core/rawdb/accessors_skipped_txs_test.go
Normal file
144
core/rawdb/accessors_skipped_txs_test.go
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
package rawdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadWriteNumSkippedTransactions(t *testing.T) {
|
||||||
|
blockNumbers := []uint64{
|
||||||
|
1,
|
||||||
|
1 << 2,
|
||||||
|
1 << 8,
|
||||||
|
1 << 16,
|
||||||
|
1 << 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
for _, num := range blockNumbers {
|
||||||
|
writeNumSkippedTransactions(db, num)
|
||||||
|
got := ReadNumSkippedTransactions(db)
|
||||||
|
|
||||||
|
if got != num {
|
||||||
|
t.Fatal("Num skipped transactions mismatch", "expected", num, "got", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestTransaction(queueIndex uint64) *types.Transaction {
|
||||||
|
l1msg := types.L1MessageTx{
|
||||||
|
QueueIndex: queueIndex,
|
||||||
|
Gas: 0,
|
||||||
|
To: &common.Address{},
|
||||||
|
Value: big.NewInt(0),
|
||||||
|
Data: nil,
|
||||||
|
Sender: common.Address{},
|
||||||
|
}
|
||||||
|
return types.NewTx(&l1msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadWriteSkippedTransactionNoIndex(t *testing.T) {
|
||||||
|
tx := newTestTransaction(123)
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
writeSkippedTransaction(db, tx, nil, "random reason", 1, &common.Hash{1})
|
||||||
|
got := ReadSkippedTransaction(db, tx.Hash())
|
||||||
|
if got == nil || got.Tx.Hash() != tx.Hash() || got.Reason != "random reason" || got.BlockNumber != 1 || got.BlockHash == nil || *got.BlockHash != (common.Hash{1}) {
|
||||||
|
t.Fatal("Skipped transaction mismatch", "got", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadSkippedTransactionV1AsV2(t *testing.T) {
|
||||||
|
tx := newTestTransaction(123)
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
writeSkippedTransactionV1(db, tx, "random reason", 1, &common.Hash{1})
|
||||||
|
got := ReadSkippedTransaction(db, tx.Hash())
|
||||||
|
if got == nil || got.Tx.Hash() != tx.Hash() || got.Reason != "random reason" || got.BlockNumber != 1 || got.BlockHash == nil || *got.BlockHash != (common.Hash{1}) {
|
||||||
|
t.Fatal("Skipped transaction mismatch", "got", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadWriteSkippedTransaction(t *testing.T) {
|
||||||
|
tx := newTestTransaction(123)
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
WriteSkippedTransaction(db, tx, nil, "random reason", 1, &common.Hash{1})
|
||||||
|
got := ReadSkippedTransaction(db, tx.Hash())
|
||||||
|
if got == nil || got.Tx.Hash() != tx.Hash() || got.Reason != "random reason" || got.BlockNumber != 1 || got.BlockHash == nil || *got.BlockHash != (common.Hash{1}) {
|
||||||
|
t.Fatal("Skipped transaction mismatch", "got", got)
|
||||||
|
}
|
||||||
|
count := ReadNumSkippedTransactions(db)
|
||||||
|
if count != 1 {
|
||||||
|
t.Fatal("Skipped transaction count mismatch", "expected", 1, "got", count)
|
||||||
|
}
|
||||||
|
hash := ReadSkippedTransactionHash(db, 0)
|
||||||
|
if hash == nil || *hash != tx.Hash() {
|
||||||
|
t.Fatal("Skipped L1 message hash mismatch", "expected", tx.Hash(), "got", hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkippedTransactionConcurrentUpdate(t *testing.T) {
|
||||||
|
count := 20
|
||||||
|
tx := newTestTransaction(123)
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for ii := 0; ii < count; ii++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
WriteSkippedTransaction(db, tx, nil, "random reason", 1, &common.Hash{1})
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
got := ReadNumSkippedTransactions(db)
|
||||||
|
if got != uint64(count) {
|
||||||
|
t.Fatal("Skipped transaction count mismatch", "expected", count, "got", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIterateSkippedTransactions(t *testing.T) {
|
||||||
|
db := NewMemoryDatabase()
|
||||||
|
|
||||||
|
txs := []*types.Transaction{
|
||||||
|
newTestTransaction(1),
|
||||||
|
newTestTransaction(2),
|
||||||
|
newTestTransaction(3),
|
||||||
|
newTestTransaction(4),
|
||||||
|
newTestTransaction(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range txs {
|
||||||
|
WriteSkippedTransaction(db, tx, nil, "random reason", 1, &common.Hash{1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// simulate skipped L2 tx that's not included in the index
|
||||||
|
l2tx := newTestTransaction(6)
|
||||||
|
writeSkippedTransaction(db, l2tx, nil, "random reason", 1, &common.Hash{1})
|
||||||
|
|
||||||
|
it := IterateSkippedTransactionsFrom(db, 2)
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
|
for ii := 2; ii < len(txs); ii++ {
|
||||||
|
finished := !it.Next()
|
||||||
|
if finished {
|
||||||
|
t.Fatal("Iterator terminated early", "ii", ii)
|
||||||
|
}
|
||||||
|
|
||||||
|
index := it.Index()
|
||||||
|
if index != uint64(ii) {
|
||||||
|
t.Fatal("Invalid skipped transaction index", "expected", ii, "got", index)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := it.TransactionHash()
|
||||||
|
if hash != txs[ii].Hash() {
|
||||||
|
t.Fatal("Invalid skipped transaction hash", "expected", txs[ii].Hash(), "got", hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finished := !it.Next()
|
||||||
|
if !finished {
|
||||||
|
t.Fatal("Iterator did not terminate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -151,6 +151,11 @@ var (
|
||||||
batchChunkRangesPrefix = []byte("R-bcr")
|
batchChunkRangesPrefix = []byte("R-bcr")
|
||||||
batchMetaPrefix = []byte("R-bm")
|
batchMetaPrefix = []byte("R-bm")
|
||||||
finalizedL2BlockNumberKey = []byte("R-finalized")
|
finalizedL2BlockNumberKey = []byte("R-finalized")
|
||||||
|
|
||||||
|
// Skipped transactions
|
||||||
|
numSkippedTransactionsKey = []byte("NumberOfSkippedTransactions")
|
||||||
|
skippedTransactionPrefix = []byte("skip") // skippedTransactionPrefix + tx hash -> skipped transaction
|
||||||
|
skippedTransactionHashPrefix = []byte("sh") // skippedTransactionHashPrefix + index -> tx hash
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use the updated "L1" prefix on all new networks
|
// Use the updated "L1" prefix on all new networks
|
||||||
|
|
@ -379,6 +384,16 @@ func FirstQueueIndexNotInL2BlockKey(l2BlockHash common.Hash) []byte {
|
||||||
return append(firstQueueIndexNotInL2BlockPrefix, l2BlockHash.Bytes()...)
|
return append(firstQueueIndexNotInL2BlockPrefix, l2BlockHash.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SkippedTransactionKey = skippedTransactionPrefix + tx hash
|
||||||
|
func SkippedTransactionKey(txHash common.Hash) []byte {
|
||||||
|
return append(skippedTransactionPrefix, txHash.Bytes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkippedTransactionHashKey = skippedTransactionHashPrefix + index (uint64 big endian)
|
||||||
|
func SkippedTransactionHashKey(index uint64) []byte {
|
||||||
|
return append(skippedTransactionHashPrefix, encodeBigEndian(index)...)
|
||||||
|
}
|
||||||
|
|
||||||
// batchChunkRangesKey = batchChunkRangesPrefix + batch index (uint64 big endian)
|
// batchChunkRangesKey = batchChunkRangesPrefix + batch index (uint64 big endian)
|
||||||
func batchChunkRangesKey(batchIndex uint64) []byte {
|
func batchChunkRangesKey(batchIndex uint64) []byte {
|
||||||
return append(batchChunkRangesPrefix, encodeBigEndian(batchIndex)...)
|
return append(batchChunkRangesPrefix, encodeBigEndian(batchIndex)...)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
// ExecutionResult includes all output after executing given evm
|
// ExecutionResult includes all output after executing given evm
|
||||||
// message no matter the execution itself is successful or not.
|
// message no matter the execution itself is successful or not.
|
||||||
type ExecutionResult struct {
|
type ExecutionResult struct {
|
||||||
|
L1DataFee *big.Int
|
||||||
UsedGas uint64 // Total used gas but include the refunded gas
|
UsedGas uint64 // Total used gas but include the refunded gas
|
||||||
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
|
Err error // Any error encountered during the execution(listed in core/vm/errors.go)
|
||||||
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
|
ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode)
|
||||||
|
|
@ -143,6 +144,9 @@ type Message struct {
|
||||||
// account nonce in state. It also disables checking that the sender is an EOA.
|
// account nonce in state. It also disables checking that the sender is an EOA.
|
||||||
// This field will be set to true for operations like RPC eth_call.
|
// This field will be set to true for operations like RPC eth_call.
|
||||||
SkipAccountChecks bool
|
SkipAccountChecks bool
|
||||||
|
|
||||||
|
// scroll-related fields
|
||||||
|
IsL1MessageTx bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionToMessage converts a transaction into a Message.
|
// TransactionToMessage converts a transaction into a Message.
|
||||||
|
|
@ -160,6 +164,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
||||||
SkipAccountChecks: false,
|
SkipAccountChecks: false,
|
||||||
BlobHashes: tx.BlobHashes(),
|
BlobHashes: tx.BlobHashes(),
|
||||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||||
|
IsL1MessageTx: tx.IsL1MessageTx(),
|
||||||
}
|
}
|
||||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||||
if baseFee != nil {
|
if baseFee != nil {
|
||||||
|
|
@ -265,6 +270,14 @@ func (st *StateTransition) buyGas() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *StateTransition) preCheck() error {
|
func (st *StateTransition) preCheck() error {
|
||||||
|
if st.msg.IsL1MessageTx {
|
||||||
|
// No fee fields to check, no nonce to check, and no need to check if EOA (L1 already verified it for us)
|
||||||
|
// Gas is free, but no refunds!
|
||||||
|
st.gasRemaining += st.msg.GasLimit
|
||||||
|
st.initialGas = st.msg.GasLimit
|
||||||
|
return st.gp.SubGas(st.msg.GasLimit) // gas used by deposits may not be used by other txs
|
||||||
|
}
|
||||||
|
|
||||||
// Only check transactions that are not fake
|
// Only check transactions that are not fake
|
||||||
msg := st.msg
|
msg := st.msg
|
||||||
if !msg.SkipAccountChecks {
|
if !msg.SkipAccountChecks {
|
||||||
|
|
@ -415,6 +428,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
||||||
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
|
ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no refunds for l1 messages
|
||||||
|
if st.msg.IsL1MessageTx {
|
||||||
|
return &ExecutionResult{
|
||||||
|
L1DataFee: big.NewInt(0),
|
||||||
|
UsedGas: st.gasUsed(),
|
||||||
|
Err: vmerr,
|
||||||
|
ReturnData: ret,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if !rules.IsLondon {
|
if !rules.IsLondon {
|
||||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||||
st.refundGas(params.RefundQuotient)
|
st.refundGas(params.RefundQuotient)
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,7 @@ type Block struct {
|
||||||
// caches
|
// caches
|
||||||
hash atomic.Value
|
hash atomic.Value
|
||||||
size atomic.Value
|
size atomic.Value
|
||||||
|
l1MsgCount atomic.Value
|
||||||
|
|
||||||
// These fields are used by package eth to track
|
// These fields are used by package eth to track
|
||||||
// inter-peer block relay.
|
// inter-peer block relay.
|
||||||
|
|
@ -505,6 +506,55 @@ func (b *Block) Hash() common.Hash {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainsL1Messages returns true if this block contains at least one L1 message.
|
||||||
|
func (b *Block) ContainsL1Messages() bool {
|
||||||
|
for _, tx := range b.transactions {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumL1MessagesProcessed returns the number of L1 messages processed in this block.
|
||||||
|
// This count includes both skipped and included messages.
|
||||||
|
// `firstQueueIndex` is the first queue index available for this block to process.
|
||||||
|
func (b *Block) NumL1MessagesProcessed(firstQueueIndex uint64) int {
|
||||||
|
if l1MsgCount := b.l1MsgCount.Load(); l1MsgCount != nil {
|
||||||
|
return l1MsgCount.(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// find first and last queue index in block
|
||||||
|
var lastQueueIndex *uint64
|
||||||
|
|
||||||
|
for ii, tx := range b.transactions {
|
||||||
|
if !tx.IsL1MessageTx() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lastQueueIndex = &b.transactions[ii].AsL1MessageTx().QueueIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate and cache L1 message count
|
||||||
|
count := 0
|
||||||
|
if lastQueueIndex != nil {
|
||||||
|
// lastQueueIndex is guaranteed to be non-nil in this case
|
||||||
|
count = int(*lastQueueIndex - firstQueueIndex + 1)
|
||||||
|
}
|
||||||
|
b.l1MsgCount.Store(count)
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountL2Tx returns the number of L2 transactions in this block.
|
||||||
|
func (b *Block) CountL2Tx() int {
|
||||||
|
count := 0
|
||||||
|
for _, tx := range b.transactions {
|
||||||
|
if !tx.IsL1MessageTx() {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
type Blocks []*Block
|
type Blocks []*Block
|
||||||
|
|
||||||
// HeaderParentHashFromRLP returns the parentHash of an RLP-encoded
|
// HeaderParentHashFromRLP returns the parentHash of an RLP-encoded
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
|
||||||
}
|
}
|
||||||
w.WriteByte(r.Type)
|
w.WriteByte(r.Type)
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case AccessListTxType, DynamicFeeTxType, BlobTxType:
|
case AccessListTxType, DynamicFeeTxType, BlobTxType, L1MessageTxType:
|
||||||
rlp.Encode(w, data)
|
rlp.Encode(w, data)
|
||||||
default:
|
default:
|
||||||
// For unsupported types, write nothing. Since this is for
|
// For unsupported types, write nothing. Since this is for
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,17 @@ package types
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -205,7 +208,7 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
|
||||||
case BlobTxType:
|
case BlobTxType:
|
||||||
inner = new(BlobTx)
|
inner = new(BlobTx)
|
||||||
case L1MessageTxType:
|
case L1MessageTxType:
|
||||||
inner = new(BlobTx)
|
inner = new(L1MessageTx)
|
||||||
default:
|
default:
|
||||||
return nil, ErrTxTypeNotSupported
|
return nil, ErrTxTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|
@ -349,6 +352,9 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
|
||||||
// Note: if the effective gasTipCap is negative, this method returns both error
|
// Note: if the effective gasTipCap is negative, this method returns both error
|
||||||
// the actual negative value, _and_ ErrGasFeeCapTooLow
|
// the actual negative value, _and_ ErrGasFeeCapTooLow
|
||||||
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
return new(big.Int), nil
|
||||||
|
}
|
||||||
if baseFee == nil {
|
if baseFee == nil {
|
||||||
return tx.GasTipCap(), nil
|
return tx.GasTipCap(), nil
|
||||||
}
|
}
|
||||||
|
|
@ -607,3 +613,45 @@ func copyAddressPtr(a *common.Address) *common.Address {
|
||||||
cpy := *a
|
cpy := *a
|
||||||
return &cpy
|
return &cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// L1MessagesByQueueIndex represents a set of L1 messages ordered by their queue indices.
|
||||||
|
type L1MessagesByQueueIndex struct {
|
||||||
|
msgs []L1MessageTx
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewL1MessagesByQueueIndex(msgs []L1MessageTx) (*L1MessagesByQueueIndex, error) {
|
||||||
|
// sort by queue index
|
||||||
|
sort.Slice(msgs, func(i, j int) bool {
|
||||||
|
return msgs[i].QueueIndex < msgs[j].QueueIndex
|
||||||
|
})
|
||||||
|
|
||||||
|
// check for duplicates/gaps
|
||||||
|
for ii := 0; ii < len(msgs)-1; ii++ {
|
||||||
|
current := msgs[ii].QueueIndex
|
||||||
|
next := msgs[ii+1].QueueIndex
|
||||||
|
if next != current+1 {
|
||||||
|
return nil, fmt.Errorf("invalid L1 message set, current index: %d, next index: %d", current, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &L1MessagesByQueueIndex{msgs: msgs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *L1MessagesByQueueIndex) Peek() *Transaction {
|
||||||
|
if len(t.msgs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return NewTx(&t.msgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *L1MessagesByQueueIndex) Shift() {
|
||||||
|
t.msgs = t.msgs[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *L1MessagesByQueueIndex) Pop() {
|
||||||
|
log.Error("Pop() is called on L1MessagesByQueueIndex")
|
||||||
|
|
||||||
|
// this is a logic error, the intention should be "Shift()",
|
||||||
|
// so we will follow the same behavior in Pop
|
||||||
|
t.Shift()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ type txJSON struct {
|
||||||
|
|
||||||
// Only used for encoding:
|
// Only used for encoding:
|
||||||
Hash common.Hash `json:"hash"`
|
Hash common.Hash `json:"hash"`
|
||||||
|
|
||||||
|
// L1 message transaction fields:
|
||||||
|
Sender common.Address `json:"sender,omitempty"`
|
||||||
|
QueueIndex *hexutil.Uint64 `json:"queueIndex,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons,
|
// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons,
|
||||||
|
|
@ -142,6 +146,14 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
||||||
enc.S = (*hexutil.Big)(itx.S.ToBig())
|
enc.S = (*hexutil.Big)(itx.S.ToBig())
|
||||||
yparity := itx.V.Uint64()
|
yparity := itx.V.Uint64()
|
||||||
enc.YParity = (*hexutil.Uint64)(&yparity)
|
enc.YParity = (*hexutil.Uint64)(&yparity)
|
||||||
|
|
||||||
|
case *L1MessageTx:
|
||||||
|
enc.QueueIndex = (*hexutil.Uint64)(&itx.QueueIndex)
|
||||||
|
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
|
||||||
|
enc.To = tx.To()
|
||||||
|
enc.Value = (*hexutil.Big)(itx.Value)
|
||||||
|
enc.Input = (*hexutil.Bytes)(&itx.Data)
|
||||||
|
enc.Sender = itx.Sender
|
||||||
}
|
}
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
@ -404,6 +416,30 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case L1MessageTxType:
|
||||||
|
var itx L1MessageTx
|
||||||
|
inner = &itx
|
||||||
|
if dec.QueueIndex == nil {
|
||||||
|
return errors.New("missing required field 'queueIndex' in transaction")
|
||||||
|
}
|
||||||
|
itx.QueueIndex = uint64(*dec.QueueIndex)
|
||||||
|
if dec.Gas == nil {
|
||||||
|
return errors.New("missing required field 'gas' in transaction")
|
||||||
|
}
|
||||||
|
itx.Gas = uint64(*dec.Gas)
|
||||||
|
if dec.To != nil {
|
||||||
|
itx.To = dec.To
|
||||||
|
}
|
||||||
|
if dec.Value == nil {
|
||||||
|
return errors.New("missing required field 'value' in transaction")
|
||||||
|
}
|
||||||
|
itx.Value = (*big.Int)(dec.Value)
|
||||||
|
if dec.Input == nil {
|
||||||
|
return errors.New("missing required field 'input' in transaction")
|
||||||
|
}
|
||||||
|
itx.Data = *dec.Input
|
||||||
|
itx.Sender = dec.Sender
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ErrTxTypeNotSupported
|
return ErrTxTypeNotSupported
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,9 @@ func NewCancunSigner(chainId *big.Int) Signer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) {
|
func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
return tx.AsL1MessageTx().Sender, nil
|
||||||
|
}
|
||||||
if tx.Type() != BlobTxType {
|
if tx.Type() != BlobTxType {
|
||||||
return s.londonSigner.Sender(tx)
|
return s.londonSigner.Sender(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -207,6 +210,9 @@ func (s cancunSigner) Equal(s2 Signer) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
return nil, nil, nil, fmt.Errorf("l1 message tx do not have a signature")
|
||||||
|
}
|
||||||
txdata, ok := tx.inner.(*BlobTx)
|
txdata, ok := tx.inner.(*BlobTx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return s.londonSigner.SignatureValues(tx, sig)
|
return s.londonSigner.SignatureValues(tx, sig)
|
||||||
|
|
@ -224,6 +230,9 @@ func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
|
||||||
// Hash returns the hash to be signed by the sender.
|
// Hash returns the hash to be signed by the sender.
|
||||||
// It does not uniquely identify the transaction.
|
// It does not uniquely identify the transaction.
|
||||||
func (s cancunSigner) Hash(tx *Transaction) common.Hash {
|
func (s cancunSigner) Hash(tx *Transaction) common.Hash {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
panic("l1 message tx cannot be signed and do not have a signing hash")
|
||||||
|
}
|
||||||
if tx.Type() != BlobTxType {
|
if tx.Type() != BlobTxType {
|
||||||
return s.londonSigner.Hash(tx)
|
return s.londonSigner.Hash(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -256,6 +265,9 @@ func NewLondonSigner(chainId *big.Int) Signer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
|
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
return tx.AsL1MessageTx().Sender, nil
|
||||||
|
}
|
||||||
if tx.Type() != DynamicFeeTxType {
|
if tx.Type() != DynamicFeeTxType {
|
||||||
return s.eip2930Signer.Sender(tx)
|
return s.eip2930Signer.Sender(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -275,6 +287,9 @@ func (s londonSigner) Equal(s2 Signer) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
return nil, nil, nil, fmt.Errorf("l1 message tx do not have a signature")
|
||||||
|
}
|
||||||
txdata, ok := tx.inner.(*DynamicFeeTx)
|
txdata, ok := tx.inner.(*DynamicFeeTx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return s.eip2930Signer.SignatureValues(tx, sig)
|
return s.eip2930Signer.SignatureValues(tx, sig)
|
||||||
|
|
@ -292,6 +307,9 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
|
||||||
// Hash returns the hash to be signed by the sender.
|
// Hash returns the hash to be signed by the sender.
|
||||||
// It does not uniquely identify the transaction.
|
// It does not uniquely identify the transaction.
|
||||||
func (s londonSigner) Hash(tx *Transaction) common.Hash {
|
func (s londonSigner) Hash(tx *Transaction) common.Hash {
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
panic("l1 message tx cannot be signed and do not have a signing hash")
|
||||||
|
}
|
||||||
if tx.Type() != DynamicFeeTxType {
|
if tx.Type() != DynamicFeeTxType {
|
||||||
return s.eip2930Signer.Hash(tx)
|
return s.eip2930Signer.Hash(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -337,6 +355,7 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) {
|
||||||
// id, add 27 to become equivalent to unprotected Homestead signatures.
|
// id, add 27 to become equivalent to unprotected Homestead signatures.
|
||||||
V = new(big.Int).Add(V, big.NewInt(27))
|
V = new(big.Int).Add(V, big.NewInt(27))
|
||||||
default:
|
default:
|
||||||
|
// L1MessageTx not supported
|
||||||
return common.Address{}, ErrTxTypeNotSupported
|
return common.Address{}, ErrTxTypeNotSupported
|
||||||
}
|
}
|
||||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||||
|
|
@ -358,6 +377,7 @@ func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
|
||||||
R, S, _ = decodeSignature(sig)
|
R, S, _ = decodeSignature(sig)
|
||||||
V = big.NewInt(int64(sig[64]))
|
V = big.NewInt(int64(sig[64]))
|
||||||
default:
|
default:
|
||||||
|
// L1MessageTx not supported
|
||||||
return nil, nil, nil, ErrTxTypeNotSupported
|
return nil, nil, nil, ErrTxTypeNotSupported
|
||||||
}
|
}
|
||||||
return R, S, V, nil
|
return R, S, V, nil
|
||||||
|
|
|
||||||
|
|
@ -611,6 +611,11 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) {
|
||||||
)
|
)
|
||||||
// Broadcast transactions to a batch of peers not knowing about it
|
// Broadcast transactions to a batch of peers not knowing about it
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
|
// L1 messages are not broadcast to peers
|
||||||
|
if tx.IsL1MessageTx() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
peers := h.peers.peersWithoutTransaction(tx.Hash())
|
peers := h.peers.peersWithoutTransaction(tx.Hash())
|
||||||
|
|
||||||
var numDirect int
|
var numDirect int
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,9 @@ type CallMsg struct {
|
||||||
Data []byte // input data, usually an ABI-encoded contract method invocation
|
Data []byte // input data, usually an ABI-encoded contract method invocation
|
||||||
|
|
||||||
AccessList types.AccessList // EIP-2930 access list.
|
AccessList types.AccessList // EIP-2930 access list.
|
||||||
|
|
||||||
|
// scroll-related:
|
||||||
|
// not need to have a `IsL1MessageTx` field, should always be false
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ContractCaller provides contract calls, essentially transactions that are executed by
|
// A ContractCaller provides contract calls, essentially transactions that are executed by
|
||||||
|
|
|
||||||
|
|
@ -1443,6 +1443,10 @@ type RPCTransaction struct {
|
||||||
R *hexutil.Big `json:"r"`
|
R *hexutil.Big `json:"r"`
|
||||||
S *hexutil.Big `json:"s"`
|
S *hexutil.Big `json:"s"`
|
||||||
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
|
YParity *hexutil.Uint64 `json:"yParity,omitempty"`
|
||||||
|
|
||||||
|
// L1 message transaction fields:
|
||||||
|
Sender common.Address `json:"sender,omitempty"`
|
||||||
|
QueueIndex *hexutil.Uint64 `json:"queueIndex,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// newRPCTransaction returns a transaction that will serialize to the RPC
|
// newRPCTransaction returns a transaction that will serialize to the RPC
|
||||||
|
|
@ -1517,6 +1521,11 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
|
||||||
}
|
}
|
||||||
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
|
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
|
||||||
result.BlobVersionedHashes = tx.BlobHashes()
|
result.BlobVersionedHashes = tx.BlobHashes()
|
||||||
|
|
||||||
|
case types.L1MessageTxType:
|
||||||
|
msg := tx.AsL1MessageTx()
|
||||||
|
result.Sender = msg.Sender
|
||||||
|
result.QueueIndex = (*hexutil.Uint64)(&msg.QueueIndex)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3790,6 +3790,9 @@ var outputTransactionFormatter = function (tx){
|
||||||
tx.maxPriorityFeePerGas = utils.toBigNumber(tx.maxPriorityFeePerGas);
|
tx.maxPriorityFeePerGas = utils.toBigNumber(tx.maxPriorityFeePerGas);
|
||||||
}
|
}
|
||||||
tx.value = utils.toBigNumber(tx.value);
|
tx.value = utils.toBigNumber(tx.value);
|
||||||
|
if(tx.queueIndex !== undefined) {
|
||||||
|
tx.queueIndex = utils.toBigNumber(tx.queueIndex);
|
||||||
|
}
|
||||||
return tx;
|
return tx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue