Commit e1193a0e authored by protolambda's avatar protolambda

span-batches: encode bitlists as big-endian integers, use standard Go big.Int...

span-batches: encode bitlists as big-endian integers, use standard Go big.Int encoding/decodig, update spec
parent 6f103820
...@@ -41,7 +41,7 @@ type spanBatchPrefix struct { ...@@ -41,7 +41,7 @@ type spanBatchPrefix struct {
type spanBatchPayload struct { type spanBatchPayload struct {
blockCount uint64 // Number of L2 block in the span blockCount uint64 // Number of L2 block in the span
originBits *big.Int // Bitlist of blockCount bits. Each bit indicates if the L1 origin is changed at the L2 block. originBits *big.Int // Standard span-batch bitlist of blockCount bits. Each bit indicates if the L1 origin is changed at the L2 block.
blockTxCounts []uint64 // List of transaction counts for each L2 block blockTxCounts []uint64 // List of transaction counts for each L2 block
txs *spanBatchTxs // Transactions encoded in SpanBatch specs txs *spanBatchTxs // Transactions encoded in SpanBatch specs
} }
...@@ -58,34 +58,12 @@ func (b *RawSpanBatch) GetBatchType() int { ...@@ -58,34 +58,12 @@ func (b *RawSpanBatch) GetBatchType() int {
} }
// decodeOriginBits parses data into bp.originBits // decodeOriginBits parses data into bp.originBits
// originBits is bitlist right-padded to a multiple of 8 bits
func (bp *spanBatchPayload) decodeOriginBits(r *bytes.Reader) error { func (bp *spanBatchPayload) decodeOriginBits(r *bytes.Reader) error {
originBitBufferLen := bp.blockCount / 8 bits, err := decodeSpanBatchBits(r, bp.blockCount)
if bp.blockCount%8 != 0 {
originBitBufferLen++
}
// avoid out of memory before allocation
if originBitBufferLen > MaxSpanBatchSize {
return ErrTooBigSpanBatchSize
}
originBitBuffer := make([]byte, originBitBufferLen)
_, err := io.ReadFull(r, originBitBuffer)
if err != nil { if err != nil {
return fmt.Errorf("failed to read origin bits: %w", err) return fmt.Errorf("failed to decode origin bits: %w", err)
}
originBits := new(big.Int)
for i := 0; i < int(bp.blockCount); i += 8 {
end := i + 8
if end > int(bp.blockCount) {
end = int(bp.blockCount)
}
bits := originBitBuffer[i/8]
for j := i; j < end; j++ {
bit := uint((bits >> (j - i)) & 1)
originBits.SetBit(originBits, j, bit)
}
} }
bp.originBits = originBits bp.originBits = bits
return nil return nil
} }
...@@ -293,26 +271,9 @@ func (bp *spanBatchPrefix) encodePrefix(w io.Writer) error { ...@@ -293,26 +271,9 @@ func (bp *spanBatchPrefix) encodePrefix(w io.Writer) error {
} }
// encodeOriginBits encodes bp.originBits // encodeOriginBits encodes bp.originBits
// originBits is bitlist right-padded to a multiple of 8 bits
func (bp *spanBatchPayload) encodeOriginBits(w io.Writer) error { func (bp *spanBatchPayload) encodeOriginBits(w io.Writer) error {
originBitBufferLen := bp.blockCount / 8 if err := encodeSpanBatchBits(w, bp.blockCount, bp.originBits); err != nil {
if bp.blockCount%8 != 0 { return fmt.Errorf("failed to encode origin bits: %w", err)
originBitBufferLen++
}
originBitBuffer := make([]byte, originBitBufferLen)
for i := 0; i < int(bp.blockCount); i += 8 {
end := i + 8
if end > int(bp.blockCount) {
end = int(bp.blockCount)
}
var bits uint = 0
for j := i; j < end; j++ {
bits |= bp.originBits.Bit(j) << (j - i)
}
originBitBuffer[i/8] = byte(bits)
}
if _, err := w.Write(originBitBuffer); err != nil {
return fmt.Errorf("cannot write origin bits: %w", err)
} }
return nil return nil
} }
......
...@@ -19,14 +19,14 @@ type spanBatchTxs struct { ...@@ -19,14 +19,14 @@ type spanBatchTxs struct {
totalBlockTxCount uint64 totalBlockTxCount uint64
// 8 fields // 8 fields
contractCreationBits *big.Int contractCreationBits *big.Int // standard span-batch bitlist
yParityBits *big.Int yParityBits *big.Int // standard span-batch bitlist
txSigs []spanBatchSignature txSigs []spanBatchSignature
txNonces []uint64 txNonces []uint64
txGases []uint64 txGases []uint64
txTos []common.Address txTos []common.Address
txDatas []hexutil.Bytes txDatas []hexutil.Bytes
protectedBits *big.Int protectedBits *big.Int // standard span-batch bitlist
// intermediate variables which can be recovered // intermediate variables which can be recovered
txTypes []int txTypes []int
...@@ -39,114 +39,35 @@ type spanBatchSignature struct { ...@@ -39,114 +39,35 @@ type spanBatchSignature struct {
s *uint256.Int s *uint256.Int
} }
// contractCreationBits is bitlist right-padded to a multiple of 8 bits
func (btx *spanBatchTxs) encodeContractCreationBits(w io.Writer) error { func (btx *spanBatchTxs) encodeContractCreationBits(w io.Writer) error {
contractCreationBitBufferLen := btx.totalBlockTxCount / 8 if err := encodeSpanBatchBits(w, btx.totalBlockTxCount, btx.contractCreationBits); err != nil {
if btx.totalBlockTxCount%8 != 0 { return fmt.Errorf("failed to encode contract creation bits: %w", err)
contractCreationBitBufferLen++
}
contractCreationBitBuffer := make([]byte, contractCreationBitBufferLen)
for i := 0; i < int(btx.totalBlockTxCount); i += 8 {
end := i + 8
if end > int(btx.totalBlockTxCount) {
end = int(btx.totalBlockTxCount)
}
var bits uint = 0
for j := i; j < end; j++ {
bits |= btx.contractCreationBits.Bit(j) << (j - i)
}
contractCreationBitBuffer[i/8] = byte(bits)
}
if _, err := w.Write(contractCreationBitBuffer); err != nil {
return fmt.Errorf("cannot write contract creation bits: %w", err)
} }
return nil return nil
} }
// contractCreationBits is bitlist right-padded to a multiple of 8 bits
func (btx *spanBatchTxs) decodeContractCreationBits(r *bytes.Reader) error { func (btx *spanBatchTxs) decodeContractCreationBits(r *bytes.Reader) error {
contractCreationBitBufferLen := btx.totalBlockTxCount / 8 bits, err := decodeSpanBatchBits(r, btx.totalBlockTxCount)
if btx.totalBlockTxCount%8 != 0 {
contractCreationBitBufferLen++
}
// avoid out of memory before allocation
if contractCreationBitBufferLen > MaxSpanBatchSize {
return ErrTooBigSpanBatchSize
}
contractCreationBitBuffer := make([]byte, contractCreationBitBufferLen)
_, err := io.ReadFull(r, contractCreationBitBuffer)
if err != nil { if err != nil {
return fmt.Errorf("failed to read contract creation bits: %w", err) return fmt.Errorf("failed to decode contract creation bits: %w", err)
} }
contractCreationBits := new(big.Int) btx.contractCreationBits = bits
for i := 0; i < int(btx.totalBlockTxCount); i += 8 {
end := i + 8
if end > int(btx.totalBlockTxCount) {
end = int(btx.totalBlockTxCount)
}
bits := contractCreationBitBuffer[i/8]
for j := i; j < end; j++ {
bit := uint((bits >> (j - i)) & 1)
contractCreationBits.SetBit(contractCreationBits, j, bit)
}
}
btx.contractCreationBits = contractCreationBits
return nil return nil
} }
// protectedBits is bitlist right-padded to a multiple of 8 bits
func (btx *spanBatchTxs) encodeProtectedBits(w io.Writer) error { func (btx *spanBatchTxs) encodeProtectedBits(w io.Writer) error {
protectedBitBufferLen := btx.totalLegacyTxCount / 8 if err := encodeSpanBatchBits(w, btx.totalLegacyTxCount, btx.protectedBits); err != nil {
if btx.totalLegacyTxCount%8 != 0 { return fmt.Errorf("failed to encode protected bits: %w", err)
protectedBitBufferLen++
}
protectedBitBuffer := make([]byte, protectedBitBufferLen)
for i := 0; i < int(btx.totalLegacyTxCount); i += 8 {
end := i + 8
if end > int(btx.totalLegacyTxCount) {
end = int(btx.totalLegacyTxCount)
}
var bits uint = 0
for j := i; j < end; j++ {
bits |= btx.protectedBits.Bit(j) << (j - i)
}
protectedBitBuffer[i/8] = byte(bits)
}
if _, err := w.Write(protectedBitBuffer); err != nil {
return fmt.Errorf("cannot write protected bits: %w", err)
} }
return nil return nil
} }
// protectedBits is bitlist right-padded to a multiple of 8 bits
func (btx *spanBatchTxs) decodeProtectedBits(r *bytes.Reader) error { func (btx *spanBatchTxs) decodeProtectedBits(r *bytes.Reader) error {
protectedBitBufferLen := btx.totalLegacyTxCount / 8 bits, err := decodeSpanBatchBits(r, btx.totalLegacyTxCount)
if btx.totalLegacyTxCount%8 != 0 {
protectedBitBufferLen++
}
// avoid out of memory before allocation
if protectedBitBufferLen > MaxSpanBatchSize {
return ErrTooBigSpanBatchSize
}
protectedBitBuffer := make([]byte, protectedBitBufferLen)
_, err := io.ReadFull(r, protectedBitBuffer)
if err != nil { if err != nil {
return fmt.Errorf("failed to read protected bits: %w", err) return fmt.Errorf("failed to decode protected bits: %w", err)
}
protectedBits := new(big.Int)
for i := 0; i < int(btx.totalLegacyTxCount); i += 8 {
end := i + 8
if end > int(btx.totalLegacyTxCount) {
end = int(btx.totalLegacyTxCount)
}
bits := protectedBitBuffer[i/8]
for j := i; j < end; j++ {
bit := uint((bits >> (j - i)) & 1)
protectedBits.SetBit(protectedBits, j, bit)
}
} }
btx.protectedBits = protectedBits btx.protectedBits = bits
return nil return nil
} }
...@@ -164,27 +85,19 @@ func (btx *spanBatchTxs) contractCreationCount() (uint64, error) { ...@@ -164,27 +85,19 @@ func (btx *spanBatchTxs) contractCreationCount() (uint64, error) {
return result, nil return result, nil
} }
// yParityBits is bitlist right-padded to a multiple of 8 bits
func (btx *spanBatchTxs) encodeYParityBits(w io.Writer) error { func (btx *spanBatchTxs) encodeYParityBits(w io.Writer) error {
yParityBitBufferLen := btx.totalBlockTxCount / 8 if err := encodeSpanBatchBits(w, btx.totalBlockTxCount, btx.yParityBits); err != nil {
if btx.totalBlockTxCount%8 != 0 { return fmt.Errorf("failed to encode y-parity bits: %w", err)
yParityBitBufferLen++
}
yParityBitBuffer := make([]byte, yParityBitBufferLen)
for i := 0; i < int(btx.totalBlockTxCount); i += 8 {
end := i + 8
if end > int(btx.totalBlockTxCount) {
end = int(btx.totalBlockTxCount)
}
var bits uint = 0
for j := i; j < end; j++ {
bits |= btx.yParityBits.Bit(j) << (j - i)
}
yParityBitBuffer[i/8] = byte(bits)
} }
if _, err := w.Write(yParityBitBuffer); err != nil { return nil
return fmt.Errorf("cannot write y parity bits: %w", err) }
func (btx *spanBatchTxs) decodeYParityBits(r *bytes.Reader) error {
bits, err := decodeSpanBatchBits(r, btx.totalBlockTxCount)
if err != nil {
return fmt.Errorf("failed to decode y-parity bits: %w", err)
} }
btx.yParityBits = bits
return nil return nil
} }
...@@ -242,37 +155,6 @@ func (btx *spanBatchTxs) encodeTxDatas(w io.Writer) error { ...@@ -242,37 +155,6 @@ func (btx *spanBatchTxs) encodeTxDatas(w io.Writer) error {
return nil return nil
} }
// yParityBits is bitlist right-padded to a multiple of 8 bits
func (btx *spanBatchTxs) decodeYParityBits(r *bytes.Reader) error {
yParityBitBufferLen := btx.totalBlockTxCount / 8
if btx.totalBlockTxCount%8 != 0 {
yParityBitBufferLen++
}
// avoid out of memory before allocation
if yParityBitBufferLen > MaxSpanBatchSize {
return ErrTooBigSpanBatchSize
}
yParityBitBuffer := make([]byte, yParityBitBufferLen)
_, err := io.ReadFull(r, yParityBitBuffer)
if err != nil {
return fmt.Errorf("failed to read y parity bits: %w", err)
}
yParityBits := new(big.Int)
for i := 0; i < int(btx.totalBlockTxCount); i += 8 {
end := i + 8
if end > int(btx.totalBlockTxCount) {
end = int(btx.totalBlockTxCount)
}
bits := yParityBitBuffer[i/8]
for j := i; j < end; j++ {
bit := uint((bits >> (j - i)) & 1)
yParityBits.SetBit(yParityBits, j, bit)
}
}
btx.yParityBits = yParityBits
return nil
}
func (btx *spanBatchTxs) decodeTxSigsRS(r *bytes.Reader) error { func (btx *spanBatchTxs) decodeTxSigsRS(r *bytes.Reader) error {
var txSigs []spanBatchSignature var txSigs []spanBatchSignature
var sigBuffer [32]byte var sigBuffer [32]byte
......
package derive
import (
"bytes"
"fmt"
"io"
"math/big"
)
// decodeSpanBatchBits decodes a standard span-batch bitlist.
// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8 bits.
// The encoded bitlist cannot be longer than MaxSpanBatchSize.
func decodeSpanBatchBits(r *bytes.Reader, bitLength uint64) (*big.Int, error) {
// Round up, ensure enough bytes when number of bits is not a multiple of 8.
// Alternative of (L+7)/8 is not overflow-safe.
bufLen := bitLength / 8
if bitLength%8 != 0 {
bufLen++
}
// avoid out of memory before allocation
if bufLen > MaxSpanBatchSize {
return nil, ErrTooBigSpanBatchSize
}
buf := make([]byte, bufLen)
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, fmt.Errorf("failed to read bits: %w", err)
}
out := new(big.Int)
out.SetBytes(buf)
// We read the correct number of bytes, but there may still be trailing bits
if l := uint64(out.BitLen()); l > bitLength {
return nil, fmt.Errorf("bitfield has %d bits, but expected no more than %d", l, bitLength)
}
return out, nil
}
// encodeSpanBatchBits encodes a standard span-batch bitlist.
// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8 bits.
// The encoded bitlist cannot be longer than MaxSpanBatchSize.
func encodeSpanBatchBits(w io.Writer, bitLength uint64, bits *big.Int) error {
if l := uint64(bits.BitLen()); l > bitLength {
return fmt.Errorf("bitfield is larger than bitLength: %d > %d", l, bitLength)
}
// Round up, ensure enough bytes when number of bits is not a multiple of 8.
// Alternative of (L+7)/8 is not overflow-safe.
bufLen := bitLength / 8
if bitLength%8 != 0 { // rounding up this way is safe against overflows
bufLen++
}
if bufLen > MaxSpanBatchSize {
return ErrTooBigSpanBatchSize
}
buf := make([]byte, bufLen)
bits.FillBytes(buf) // zero-extended, big-endian
if _, err := w.Write(buf); err != nil {
return fmt.Errorf("cannot write bits: %w", err)
}
return nil
}
...@@ -86,6 +86,9 @@ Notation: ...@@ -86,6 +86,9 @@ Notation:
[protobuf spec]: https://protobuf.dev/programming-guides/encoding/#varints [protobuf spec]: https://protobuf.dev/programming-guides/encoding/#varints
Standard bitlists, in the context of span-batches, are encoded as big-endian integers,
left-padded with zeroes to the next multiple of 8 bits.
Where: Where:
- `prefix = rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check` - `prefix = rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check`
...@@ -98,15 +101,15 @@ Where: ...@@ -98,15 +101,15 @@ Where:
The hash is truncated to 20 bytes for efficiency, i.e. `span_end.l1_origin.hash[:20]`. The hash is truncated to 20 bytes for efficiency, i.e. `span_end.l1_origin.hash[:20]`.
- `payload = block_count ++ origin_bits ++ block_tx_counts ++ txs`: - `payload = block_count ++ origin_bits ++ block_tx_counts ++ txs`:
- `block_count`: `uvarint` number of L2 blocks. This is at least 1, empty span batches are invalid. - `block_count`: `uvarint` number of L2 blocks. This is at least 1, empty span batches are invalid.
- `origin_bits`: bitlist of `block_count` bits, right-padded to a multiple of 8 bits: - `origin_bits`: standard bitlist of `block_count` bits:
1 bit per L2 block, indicating if the L1 origin changed this L2 block. 1 bit per L2 block, indicating if the L1 origin changed this L2 block.
- `block_tx_counts`: for each block, a `uvarint` of `len(block.transactions)`. - `block_tx_counts`: for each block, a `uvarint` of `len(block.transactions)`.
- `txs`: L2 transactions which is reorganized and encoded as below. - `txs`: L2 transactions which is reorganized and encoded as below.
- `txs = contract_creation_bits ++ y_parity_bits ++ - `txs = contract_creation_bits ++ y_parity_bits ++
tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits` tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits`
- `contract_creation_bits`: bit list of `sum(block_tx_counts)` bits, right-padded to a multiple of 8 bits, - `contract_creation_bits`: standard bitlist of `sum(block_tx_counts)` bits:
1 bit per L2 transactions, indicating if transaction is a contract creation transaction. 1 bit per L2 transactions, indicating if transaction is a contract creation transaction.
- `y_parity_bits`: bit list of `sum(block_tx_counts)` bits, right-padded to a multiple of 8 bits, - `y_parity_bits`: standard bitlist of `sum(block_tx_counts)` bits:
1 bit per L2 transactions, indicating the y parity value when recovering transaction sender address. 1 bit per L2 transactions, indicating the y parity value when recovering transaction sender address.
- `tx_sigs`: concatenated list of transaction signatures - `tx_sigs`: concatenated list of transaction signatures
- `r` is encoded as big-endian `uint256` - `r` is encoded as big-endian `uint256`
...@@ -122,7 +125,7 @@ Where: ...@@ -122,7 +125,7 @@ Where:
- `legacy`: `gasLimit` - `legacy`: `gasLimit`
- `1`: ([EIP-2930]): `gasLimit` - `1`: ([EIP-2930]): `gasLimit`
- `2`: ([EIP-1559]): `gas_limit` - `2`: ([EIP-1559]): `gas_limit`
- `protected_bits`: bit list of length of number of legacy transactions, right-padded to a multiple of 8 bits, - `protected_bits`: standard bitlist of length of number of legacy transactions:
1 bit per L2 legacy transactions, indicating if transacion is protected([EIP-155]) or not. 1 bit per L2 legacy transactions, indicating if transacion is protected([EIP-155]) or not.
Introduce version `2` to the [batch-format](./derivation.md#batch-format) table: Introduce version `2` to the [batch-format](./derivation.md#batch-format) table:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment