Files
linea-monorepo/prover/lib/compressor/blob/v1/encode.go
AlexandreBelling 89c048bd29 Prover: fix empty to address decoding (#403)
* test and fix the encoding-decoding of transaction with an empty to

* fix typo

* readd the explicit test case

* fix: move decompressBlob out of the "no-CI" build tag

* fix tag confusion

* Fix prover libs CI release  (#450)

* prover: try to fix native libs arm build

---------

Signed-off-by: AlexandreBelling <alexandrebelling8@gmail.com>
Co-authored-by: Arya Tabaie <arya.pourtabatabaie@gmail.com>
Co-authored-by: Pedro Novais <1478752+jpnovais@users.noreply.github.com>
2024-12-16 13:31:53 +01:00

212 lines
6.2 KiB
Go

package v1
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/consensys/linea-monorepo/prover/backend/ethereum"
"github.com/consensys/linea-monorepo/prover/lib/compressor/blob/encode"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// EncodeBlockForCompression encodes a block for compression.
func EncodeBlockForCompression(block *types.Block, w io.Writer) error {
if block == nil {
return fmt.Errorf("block is nil")
}
if block.Transactions() == nil {
return fmt.Errorf("block has nil transactions")
}
var (
// timestamp is the timestamp of the encoded block. The timestamp is
// encoded over a uint32 as this is sufficient in practice.
timestamp = uint32(block.Time())
// transactions lists the txs of the encoded block.
transactions = block.Transactions()
// blkHash holds the block hash
blockHash = block.Hash()
// numTxs holds the number of transaction. We use a uint16 as this is a
// realistic maximal value to store the transactions.
numTxs = uint16(len(transactions))
)
binary.Write(w, binary.BigEndian, numTxs)
binary.Write(w, binary.BigEndian, timestamp)
w.Write(blockHash[:])
for i, tx := range transactions {
if err := EncodeTxForCompression(tx, w); err != nil {
return fmt.Errorf("could not encode transaction #%v: %w", i, err)
}
}
return nil
}
// encodeTransaction encodes a single transaction
func EncodeTxForCompression(tx *types.Transaction, w io.Writer) error {
if tx == nil {
return fmt.Errorf("transactions is nil")
}
var (
from = ethereum.GetFrom(tx)
txRlp = ethereum.EncodeTxForSigning(tx)
_, err1 = w.Write(from[:])
_, err2 = w.Write(txRlp[:])
)
return errors.Join(err1, err2)
}
// ScanBlockByteLen scans the stream of bytes `b`, expecting to find an encoded
// block starting from position 0 and returns the length of the block. It returns
// an error if the scanner goes out of bound.
func ScanBlockByteLen(b []byte) (int, error) {
const (
// preTxBufSize corresponds to the size of the buffer area used for
// encoding the block-hash and the timestamp. They are are stored ahead
// of the transaction
preTxBufSize = 32 + 4
// heuristicMaxNbTxs corresponds to a tacit maximal value that we can
// expect to be contained in the currently scanned block. The theoritical
// max value is 2**16 but a value higher than heuristic value is
// considered "odd" and triggers an error. The check can be easily remove
// in the future if we decide to make large blocks. This is used as an
// early fail mechanism.
heuristicMaxNbTxs = 1 << 10
)
var (
r = bytes.NewReader(b)
// decNumTxs corresponds to the number of transaction that are contained
// in the currently scanned block.
decNumTxs uint16
)
if err := binary.Read(r, binary.BigEndian, &decNumTxs); err != nil {
return 0, fmt.Errorf("could not decode nb txs: %w", err)
}
if decNumTxs > heuristicMaxNbTxs {
return 0, fmt.Errorf("invalid block: the decoded nb tx is %v > %v", decNumTxs, heuristicMaxNbTxs)
}
r.Seek(preTxBufSize, io.SeekCurrent)
for i := 0; i < int(decNumTxs); i++ {
// Pass the tx from address
r.Seek(int64(len(common.Address{})), io.SeekCurrent)
// The transaction of type dynamicFee and accessList have a prefix ahead
// the RLP encoding. When that is the case, we need to skip it before
// scanning the RLP prefix of the transaction.
prefix, err := r.ReadByte()
if err != nil {
return 0, fmt.Errorf("could not read the prefix byte of the tx #%v", i)
}
// When the prefix does not match a transaction type, this indicates a
// legacy transaction and the prefix was in fact the RLP prefix.
// We unread it, so that [passRlpTx] can access it.
if int(prefix) > types.DynamicFeeTxType {
r.UnreadByte()
}
if err := PassRlpList(r); err != nil {
return 0, fmt.Errorf("failed passing transaction #%v: %w", i, err)
}
}
return int(r.Size()) - r.Len(), nil
}
// PassRlpList advances the reader through an RLP list assuming the reader is
// currently pointing to an RLP-encoded list. This is used to scan the encoded
// number of bytes of an encoded blocks and more specifically to find the
// boundaries of an RLP-encoded transaction.
func PassRlpList(r *bytes.Reader) error {
firstByte, err := r.ReadByte()
if err != nil {
return fmt.Errorf("could not read the first byte: %w", err)
}
if firstByte < 0xc0 {
return fmt.Errorf("not an RLP list, the first byte is `0x%x`", firstByte)
}
if firstByte <= 0xf7 {
payLoadLen := int(firstByte) - 0xc0
r.Seek(int64(payLoadLen), io.SeekCurrent)
return nil
}
if firstByte > 0xf7 {
l := int(firstByte) - 0xf7
var buf [8]byte
r.Read(buf[8-l:])
payloadLen := binary.BigEndian.Uint64(buf[:])
r.Seek(int64(payloadLen), io.SeekCurrent)
}
return nil
}
// DecodeBlockFromUncompressed inverts [EncodeBlockForCompression]. It is primarily meant for
// testing and ensuring the encoding is bijective.
func DecodeBlockFromUncompressed(r *bytes.Reader) (encode.DecodedBlockData, error) {
var (
decNumTxs uint16
decTimestamp uint32
blockHash common.Hash
)
if err := binary.Read(r, binary.BigEndian, &decNumTxs); err != nil {
return encode.DecodedBlockData{}, fmt.Errorf("could not decode nb txs: %w", err)
}
if err := binary.Read(r, binary.BigEndian, &decTimestamp); err != nil {
return encode.DecodedBlockData{}, fmt.Errorf("could not decode timestamp: %w", err)
}
if _, err := r.Read(blockHash[:]); err != nil {
return encode.DecodedBlockData{}, fmt.Errorf("could not read the block hash: %w", err)
}
numTxs := int(decNumTxs)
decodedBlk := encode.DecodedBlockData{
Froms: make([]common.Address, numTxs),
Txs: make([]types.TxData, numTxs),
Timestamp: uint64(decTimestamp),
BlockHash: blockHash,
}
var err error
for i := 0; i < int(decNumTxs); i++ {
if decodedBlk.Txs[i], err = DecodeTxFromUncompressed(r, &decodedBlk.Froms[i]); err != nil {
return encode.DecodedBlockData{}, fmt.Errorf("could not decode transaction #%v: %w", i, err)
}
}
return decodedBlk, nil
}
func DecodeTxFromUncompressed(r *bytes.Reader, from *common.Address) (types.TxData, error) {
if _, err := r.Read(from[:]); err != nil {
return nil, fmt.Errorf("could not read from address: %w", err)
}
return ethereum.DecodeTxFromBytes(r)
}