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,
|
||||
AccessList: call.AccessList,
|
||||
SkipAccountChecks: true,
|
||||
IsL1MessageTx: false,
|
||||
}
|
||||
|
||||
// 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 = 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"
|
||||
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
|
@ -124,6 +126,97 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -319,6 +319,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
|||
return nil, ErrNoGenesis
|
||||
}
|
||||
|
||||
// initialize L1 message index for genesis block
|
||||
rawdb.WriteFirstQueueIndexNotInL2Block(db, bc.genesisBlock.Hash(), 0)
|
||||
|
||||
bc.currentBlock.Store(nil)
|
||||
bc.currentSnapBlock.Store(nil)
|
||||
bc.currentFinalBlock.Store(nil)
|
||||
|
|
@ -866,6 +869,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
|||
batch := bc.db.NewBatch()
|
||||
rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty())
|
||||
rawdb.WriteBlock(batch, genesis)
|
||||
rawdb.WriteFirstQueueIndexNotInL2Block(batch, genesis.Hash(), 0)
|
||||
if err := batch.Write(); err != nil {
|
||||
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()
|
||||
rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td)
|
||||
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 {
|
||||
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.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
||||
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 {
|
||||
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
|
||||
}
|
||||
// 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)) {
|
||||
log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash())
|
||||
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.WriteHeadHeaderHash(db, block.Hash())
|
||||
rawdb.WriteChainConfig(db, block.Hash(), config)
|
||||
rawdb.WriteFirstQueueIndexNotInL2Block(db, block.Hash(), 0)
|
||||
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")
|
||||
batchMetaPrefix = []byte("R-bm")
|
||||
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
|
||||
|
|
@ -379,6 +384,16 @@ func FirstQueueIndexNotInL2BlockKey(l2BlockHash common.Hash) []byte {
|
|||
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)
|
||||
func batchChunkRangesKey(batchIndex uint64) []byte {
|
||||
return append(batchChunkRangesPrefix, encodeBigEndian(batchIndex)...)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import (
|
|||
// ExecutionResult includes all output after executing given evm
|
||||
// message no matter the execution itself is successful or not.
|
||||
type ExecutionResult struct {
|
||||
L1DataFee *big.Int
|
||||
UsedGas uint64 // Total used gas but include the refunded gas
|
||||
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)
|
||||
|
|
@ -143,6 +144,9 @@ type Message struct {
|
|||
// 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.
|
||||
SkipAccountChecks bool
|
||||
|
||||
// scroll-related fields
|
||||
IsL1MessageTx bool
|
||||
}
|
||||
|
||||
// TransactionToMessage converts a transaction into a Message.
|
||||
|
|
@ -160,6 +164,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
|||
SkipAccountChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
IsL1MessageTx: tx.IsL1MessageTx(),
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
|
|
@ -265,6 +270,14 @@ func (st *StateTransition) buyGas() 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
|
||||
msg := st.msg
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||
st.refundGas(params.RefundQuotient)
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ type Block struct {
|
|||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
l1MsgCount atomic.Value
|
||||
|
||||
// These fields are used by package eth to track
|
||||
// inter-peer block relay.
|
||||
|
|
@ -505,6 +506,55 @@ func (b *Block) Hash() common.Hash {
|
|||
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
|
||||
|
||||
// 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)
|
||||
switch r.Type {
|
||||
case AccessListTxType, DynamicFeeTxType, BlobTxType:
|
||||
case AccessListTxType, DynamicFeeTxType, BlobTxType, L1MessageTxType:
|
||||
rlp.Encode(w, data)
|
||||
default:
|
||||
// For unsupported types, write nothing. Since this is for
|
||||
|
|
|
|||
|
|
@ -19,14 +19,17 @@ package types
|
|||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
|
|
@ -205,7 +208,7 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
|
|||
case BlobTxType:
|
||||
inner = new(BlobTx)
|
||||
case L1MessageTxType:
|
||||
inner = new(BlobTx)
|
||||
inner = new(L1MessageTx)
|
||||
default:
|
||||
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
|
||||
// the actual negative value, _and_ ErrGasFeeCapTooLow
|
||||
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
||||
if tx.IsL1MessageTx() {
|
||||
return new(big.Int), nil
|
||||
}
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCap(), nil
|
||||
}
|
||||
|
|
@ -607,3 +613,45 @@ func copyAddressPtr(a *common.Address) *common.Address {
|
|||
cpy := *a
|
||||
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:
|
||||
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,
|
||||
|
|
@ -142,6 +146,14 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
|||
enc.S = (*hexutil.Big)(itx.S.ToBig())
|
||||
yparity := itx.V.Uint64()
|
||||
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)
|
||||
}
|
||||
|
|
@ -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:
|
||||
return ErrTxTypeNotSupported
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,6 +188,9 @@ func NewCancunSigner(chainId *big.Int) Signer {
|
|||
}
|
||||
|
||||
func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) {
|
||||
if tx.IsL1MessageTx() {
|
||||
return tx.AsL1MessageTx().Sender, nil
|
||||
}
|
||||
if tx.Type() != BlobTxType {
|
||||
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) {
|
||||
if tx.IsL1MessageTx() {
|
||||
return nil, nil, nil, fmt.Errorf("l1 message tx do not have a signature")
|
||||
}
|
||||
txdata, ok := tx.inner.(*BlobTx)
|
||||
if !ok {
|
||||
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.
|
||||
// It does not uniquely identify the transaction.
|
||||
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 {
|
||||
return s.londonSigner.Hash(tx)
|
||||
}
|
||||
|
|
@ -256,6 +265,9 @@ func NewLondonSigner(chainId *big.Int) Signer {
|
|||
}
|
||||
|
||||
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
|
||||
if tx.IsL1MessageTx() {
|
||||
return tx.AsL1MessageTx().Sender, nil
|
||||
}
|
||||
if tx.Type() != DynamicFeeTxType {
|
||||
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) {
|
||||
if tx.IsL1MessageTx() {
|
||||
return nil, nil, nil, fmt.Errorf("l1 message tx do not have a signature")
|
||||
}
|
||||
txdata, ok := tx.inner.(*DynamicFeeTx)
|
||||
if !ok {
|
||||
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.
|
||||
// It does not uniquely identify the transaction.
|
||||
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 {
|
||||
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.
|
||||
V = new(big.Int).Add(V, big.NewInt(27))
|
||||
default:
|
||||
// L1MessageTx not supported
|
||||
return common.Address{}, ErrTxTypeNotSupported
|
||||
}
|
||||
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)
|
||||
V = big.NewInt(int64(sig[64]))
|
||||
default:
|
||||
// L1MessageTx not supported
|
||||
return nil, nil, nil, ErrTxTypeNotSupported
|
||||
}
|
||||
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
|
||||
for _, tx := range txs {
|
||||
// L1 messages are not broadcast to peers
|
||||
if tx.IsL1MessageTx() {
|
||||
continue
|
||||
}
|
||||
|
||||
peers := h.peers.peersWithoutTransaction(tx.Hash())
|
||||
|
||||
var numDirect int
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ type CallMsg struct {
|
|||
Data []byte // input data, usually an ABI-encoded contract method invocation
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1443,6 +1443,10 @@ type RPCTransaction struct {
|
|||
R *hexutil.Big `json:"r"`
|
||||
S *hexutil.Big `json:"s"`
|
||||
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
|
||||
|
|
@ -1517,6 +1521,11 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
|
|||
}
|
||||
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
|
||||
result.BlobVersionedHashes = tx.BlobHashes()
|
||||
|
||||
case types.L1MessageTxType:
|
||||
msg := tx.AsL1MessageTx()
|
||||
result.Sender = msg.Sender
|
||||
result.QueueIndex = (*hexutil.Uint64)(&msg.QueueIndex)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3790,6 +3790,9 @@ var outputTransactionFormatter = function (tx){
|
|||
tx.maxPriorityFeePerGas = utils.toBigNumber(tx.maxPriorityFeePerGas);
|
||||
}
|
||||
tx.value = utils.toBigNumber(tx.value);
|
||||
if(tx.queueIndex !== undefined) {
|
||||
tx.queueIndex = utils.toBigNumber(tx.queueIndex);
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue