package rollup_sync_service import ( "encoding/binary" "errors" "fmt" "math" "math/big" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/core/types" ) const blockContextByteSize = 60 // WrappedBlock contains the block's Header, Transactions and WithdrawTrieRoot hash. type WrappedBlock struct { Header *types.Header `json:"header"` // Transactions is only used for recover types.Transactions, the from of types.TransactionData field is missing. Transactions []*types.TransactionData `json:"transactions"` WithdrawRoot common.Hash `json:"withdraw_trie_root,omitempty"` } // BlockContext represents the essential data of a block in the ScrollChain. // It provides an overview of block attributes including hash values, block numbers, gas details, and transaction counts. type BlockContext struct { BlockHash common.Hash ParentHash common.Hash BlockNumber uint64 Timestamp uint64 BaseFee *big.Int GasLimit uint64 NumTransactions uint16 NumL1Messages uint16 } // numL1Messages returns the number of L1 messages in this block. // This number is the sum of included and skipped L1 messages. func (w *WrappedBlock) numL1Messages(totalL1MessagePoppedBefore uint64) uint64 { var lastQueueIndex *uint64 for _, txData := range w.Transactions { if txData.Type == types.L1MessageTxType { lastQueueIndex = &txData.Nonce } } if lastQueueIndex == nil { return 0 } // note: last queue index included before this block is totalL1MessagePoppedBefore - 1 // TODO: cache results return *lastQueueIndex - totalL1MessagePoppedBefore + 1 } // Encode encodes the WrappedBlock into RollupV2 BlockContext Encoding. func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) { bytes := make([]byte, 60) if !w.Header.Number.IsUint64() { return nil, errors.New("block number is not uint64") } // note: numL1Messages includes skipped messages numL1Messages := w.numL1Messages(totalL1MessagePoppedBefore) if numL1Messages > math.MaxUint16 { return nil, errors.New("number of L1 messages exceeds max uint16") } // note: numTransactions includes skipped messages numL2Transactions := w.numL2Transactions() numTransactions := numL1Messages + numL2Transactions if numTransactions > math.MaxUint16 { return nil, errors.New("number of transactions exceeds max uint16") } binary.BigEndian.PutUint64(bytes[0:], w.Header.Number.Uint64()) binary.BigEndian.PutUint64(bytes[8:], w.Header.Time) // TODO: [16:47] Currently, baseFee is 0, because we disable EIP-1559. binary.BigEndian.PutUint64(bytes[48:], w.Header.GasLimit) binary.BigEndian.PutUint16(bytes[56:], uint16(numTransactions)) binary.BigEndian.PutUint16(bytes[58:], uint16(numL1Messages)) return bytes, nil } func txsToTxsData(txs types.Transactions) []*types.TransactionData { txsData := make([]*types.TransactionData, len(txs)) for i, tx := range txs { v, r, s := tx.RawSignatureValues() nonce := tx.Nonce() // We need QueueIndex in `NewBatchHeader`. However, `TransactionData` // does not have this field. Since `L1MessageTx` do not have a nonce, // we reuse this field for storing the queue index. if msg := tx.AsL1MessageTx(); msg != nil { nonce = msg.QueueIndex } txsData[i] = &types.TransactionData{ Type: tx.Type(), TxHash: tx.Hash().String(), Nonce: nonce, ChainId: (*hexutil.Big)(tx.ChainId()), Gas: tx.Gas(), GasPrice: (*hexutil.Big)(tx.GasPrice()), To: tx.To(), Value: (*hexutil.Big)(tx.Value()), Data: hexutil.Encode(tx.Data()), IsCreate: tx.To() == nil, V: (*hexutil.Big)(v), R: (*hexutil.Big)(r), S: (*hexutil.Big)(s), } } return txsData } func convertTxDataToRLPEncoding(txData *types.TransactionData) ([]byte, error) { data, err := hexutil.Decode(txData.Data) if err != nil { return nil, fmt.Errorf("failed to decode txData.Data: %s, err: %w", txData.Data, err) } tx := types.NewTx(&types.LegacyTx{ Nonce: txData.Nonce, To: txData.To, Value: txData.Value.ToInt(), Gas: txData.Gas, GasPrice: txData.GasPrice.ToInt(), Data: data, V: txData.V.ToInt(), R: txData.R.ToInt(), S: txData.S.ToInt(), }) rlpTxData, err := tx.MarshalBinary() if err != nil { return nil, fmt.Errorf("failed to marshal binary of the tx: %+v, err: %w", tx, err) } return rlpTxData, nil } func (w *WrappedBlock) numL2Transactions() uint64 { var count uint64 for _, txData := range w.Transactions { if txData.Type != types.L1MessageTxType { count++ } } return count } func decodeBlockContext(encodedBlockContext []byte) (*BlockContext, error) { if len(encodedBlockContext) != blockContextByteSize { return nil, errors.New("block encoding is not 60 bytes long") } return &BlockContext{ BlockNumber: binary.BigEndian.Uint64(encodedBlockContext[0:8]), Timestamp: binary.BigEndian.Uint64(encodedBlockContext[8:16]), GasLimit: binary.BigEndian.Uint64(encodedBlockContext[48:56]), NumTransactions: binary.BigEndian.Uint16(encodedBlockContext[56:58]), NumL1Messages: binary.BigEndian.Uint16(encodedBlockContext[58:60]), }, nil }