Commit afbe2e89 authored by Joshua Gutow's avatar Joshua Gutow Committed by GitHub

Merge pull request #8458 from testinprod-io/tip/span-batch-protected-tx-patch

op-node: Span batch Unprotected Tx Handling + Logic Error Fix
parents b669f328 6c316744
...@@ -19,7 +19,7 @@ import ( ...@@ -19,7 +19,7 @@ import (
) )
func RandomRawSpanBatch(rng *rand.Rand, chainId *big.Int) *RawSpanBatch { func RandomRawSpanBatch(rng *rand.Rand, chainId *big.Int) *RawSpanBatch {
blockCount := uint64(1 + rng.Int()&0xFF) blockCount := uint64(4 + rng.Int()&0xFF) // at least 4
originBits := new(big.Int) originBits := new(big.Int)
for i := 0; i < int(blockCount); i++ { for i := 0; i < int(blockCount); i++ {
bit := uint(0) bit := uint(0)
...@@ -31,14 +31,24 @@ func RandomRawSpanBatch(rng *rand.Rand, chainId *big.Int) *RawSpanBatch { ...@@ -31,14 +31,24 @@ func RandomRawSpanBatch(rng *rand.Rand, chainId *big.Int) *RawSpanBatch {
var blockTxCounts []uint64 var blockTxCounts []uint64
totalblockTxCounts := uint64(0) totalblockTxCounts := uint64(0)
for i := 0; i < int(blockCount); i++ { for i := 0; i < int(blockCount); i++ {
blockTxCount := uint64(rng.Intn(16)) blockTxCount := 1 + uint64(rng.Intn(16))
blockTxCounts = append(blockTxCounts, blockTxCount) blockTxCounts = append(blockTxCounts, blockTxCount)
totalblockTxCounts += blockTxCount totalblockTxCounts += blockTxCount
} }
signer := types.NewLondonSigner(chainId) londonSigner := types.NewLondonSigner(chainId)
var txs [][]byte var txs [][]byte
for i := 0; i < int(totalblockTxCounts); i++ { for i := 0; i < int(totalblockTxCounts); i++ {
tx := testutils.RandomTx(rng, new(big.Int).SetUint64(rng.Uint64()), signer) var tx *types.Transaction
switch i % 4 {
case 0:
tx = testutils.RandomLegacyTx(rng, types.HomesteadSigner{})
case 1:
tx = testutils.RandomLegacyTx(rng, londonSigner)
case 2:
tx = testutils.RandomAccessListTx(rng, londonSigner)
case 3:
tx = testutils.RandomDynamicFeeTx(rng, londonSigner)
}
rawTx, err := tx.MarshalBinary() rawTx, err := tx.MarshalBinary()
if err != nil { if err != nil {
panic("MarshalBinary:" + err.Error()) panic("MarshalBinary:" + err.Error())
......
...@@ -26,7 +26,7 @@ import ( ...@@ -26,7 +26,7 @@ import (
// spanBatch := SpanBatchType ++ prefix ++ payload // spanBatch := SpanBatchType ++ prefix ++ payload
// prefix := rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check // prefix := rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check
// payload := block_count ++ origin_bits ++ block_tx_counts ++ txs // payload := block_count ++ origin_bits ++ block_tx_counts ++ txs
// txs := contract_creation_bits ++ y_parity_bits ++ tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases // txs := contract_creation_bits ++ y_parity_bits ++ tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits
var ErrTooBigSpanBatchSize = errors.New("span batch size limit reached") var ErrTooBigSpanBatchSize = errors.New("span batch size limit reached")
...@@ -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
} }
......
...@@ -447,9 +447,10 @@ func TestSpanBatchToSingularBatch(t *testing.T) { ...@@ -447,9 +447,10 @@ func TestSpanBatchToSingularBatch(t *testing.T) {
func TestSpanBatchReadTxData(t *testing.T) { func TestSpanBatchReadTxData(t *testing.T) {
cases := []spanBatchTxTest{ cases := []spanBatchTxTest{
{"legacy tx", 32, testutils.RandomLegacyTx}, {"unprotected legacy tx", 32, testutils.RandomLegacyTx, false},
{"access list tx", 32, testutils.RandomAccessListTx}, {"legacy tx", 32, testutils.RandomLegacyTx, true},
{"dynamic fee tx", 32, testutils.RandomDynamicFeeTx}, {"access list tx", 32, testutils.RandomAccessListTx, true},
{"dynamic fee tx", 32, testutils.RandomDynamicFeeTx, true},
} }
for i, testCase := range cases { for i, testCase := range cases {
...@@ -457,6 +458,9 @@ func TestSpanBatchReadTxData(t *testing.T) { ...@@ -457,6 +458,9 @@ func TestSpanBatchReadTxData(t *testing.T) {
rng := rand.New(rand.NewSource(int64(0x109550 + i))) rng := rand.New(rand.NewSource(int64(0x109550 + i)))
chainID := new(big.Int).SetUint64(rng.Uint64()) chainID := new(big.Int).SetUint64(rng.Uint64())
signer := types.NewLondonSigner(chainID) signer := types.NewLondonSigner(chainID)
if !testCase.protected {
signer = types.HomesteadSigner{}
}
var rawTxs [][]byte var rawTxs [][]byte
var txs []*types.Transaction var txs []*types.Transaction
......
...@@ -12,16 +12,18 @@ import ( ...@@ -12,16 +12,18 @@ import (
) )
type spanBatchTxTest struct { type spanBatchTxTest struct {
name string name string
trials int trials int
mkTx func(rng *rand.Rand, signer types.Signer) *types.Transaction mkTx func(rng *rand.Rand, signer types.Signer) *types.Transaction
protected bool
} }
func TestSpanBatchTxConvert(t *testing.T) { func TestSpanBatchTxConvert(t *testing.T) {
cases := []spanBatchTxTest{ cases := []spanBatchTxTest{
{"legacy tx", 32, testutils.RandomLegacyTx}, {"unprotected legacy tx", 32, testutils.RandomLegacyTx, false},
{"access list tx", 32, testutils.RandomAccessListTx}, {"legacy tx", 32, testutils.RandomLegacyTx, true},
{"dynamic fee tx", 32, testutils.RandomDynamicFeeTx}, {"access list tx", 32, testutils.RandomAccessListTx, true},
{"dynamic fee tx", 32, testutils.RandomDynamicFeeTx, true},
} }
for i, testCase := range cases { for i, testCase := range cases {
...@@ -29,6 +31,9 @@ func TestSpanBatchTxConvert(t *testing.T) { ...@@ -29,6 +31,9 @@ func TestSpanBatchTxConvert(t *testing.T) {
rng := rand.New(rand.NewSource(int64(0x1331 + i))) rng := rand.New(rand.NewSource(int64(0x1331 + i)))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
signer := types.NewLondonSigner(chainID) signer := types.NewLondonSigner(chainID)
if !testCase.protected {
signer = types.HomesteadSigner{}
}
for txIdx := 0; txIdx < testCase.trials; txIdx++ { for txIdx := 0; txIdx < testCase.trials; txIdx++ {
tx := testCase.mkTx(rng, signer) tx := testCase.mkTx(rng, signer)
...@@ -54,9 +59,10 @@ func TestSpanBatchTxConvert(t *testing.T) { ...@@ -54,9 +59,10 @@ func TestSpanBatchTxConvert(t *testing.T) {
func TestSpanBatchTxRoundTrip(t *testing.T) { func TestSpanBatchTxRoundTrip(t *testing.T) {
cases := []spanBatchTxTest{ cases := []spanBatchTxTest{
{"legacy tx", 32, testutils.RandomLegacyTx}, {"unprotected legacy tx", 32, testutils.RandomLegacyTx, false},
{"access list tx", 32, testutils.RandomAccessListTx}, {"legacy tx", 32, testutils.RandomLegacyTx, true},
{"dynamic fee tx", 32, testutils.RandomDynamicFeeTx}, {"access list tx", 32, testutils.RandomAccessListTx, true},
{"dynamic fee tx", 32, testutils.RandomDynamicFeeTx, true},
} }
for i, testCase := range cases { for i, testCase := range cases {
...@@ -64,6 +70,9 @@ func TestSpanBatchTxRoundTrip(t *testing.T) { ...@@ -64,6 +70,9 @@ func TestSpanBatchTxRoundTrip(t *testing.T) {
rng := rand.New(rand.NewSource(int64(0x1332 + i))) rng := rand.New(rand.NewSource(int64(0x1332 + i)))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
signer := types.NewLondonSigner(chainID) signer := types.NewLondonSigner(chainID)
if !testCase.protected {
signer = types.HomesteadSigner{}
}
for txIdx := 0; txIdx < testCase.trials; txIdx++ { for txIdx := 0; txIdx < testCase.trials; txIdx++ {
tx := testCase.mkTx(rng, signer) tx := testCase.mkTx(rng, signer)
......
...@@ -18,16 +18,19 @@ type spanBatchTxs struct { ...@@ -18,16 +18,19 @@ type spanBatchTxs struct {
// this field must be manually set // this field must be manually set
totalBlockTxCount uint64 totalBlockTxCount uint64
// 7 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 // standard span-batch bitlist
txTypes []int // intermediate variables which can be recovered
txTypes []int
totalLegacyTxCount uint64
} }
type spanBatchSignature struct { type spanBatchSignature struct {
...@@ -36,58 +39,35 @@ type spanBatchSignature struct { ...@@ -36,58 +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 { if err != nil {
contractCreationBitBufferLen++ return fmt.Errorf("failed to decode contract creation bits: %w", err)
} }
// avoid out of memory before allocation btx.contractCreationBits = bits
if contractCreationBitBufferLen > MaxSpanBatchSize { return nil
return ErrTooBigSpanBatchSize }
func (btx *spanBatchTxs) encodeProtectedBits(w io.Writer) error {
if err := encodeSpanBatchBits(w, btx.totalLegacyTxCount, btx.protectedBits); err != nil {
return fmt.Errorf("failed to encode protected bits: %w", err)
} }
contractCreationBitBuffer := make([]byte, contractCreationBitBufferLen) return nil
_, err := io.ReadFull(r, contractCreationBitBuffer) }
func (btx *spanBatchTxs) decodeProtectedBits(r *bytes.Reader) error {
bits, err := decodeSpanBatchBits(r, btx.totalLegacyTxCount)
if err != nil { if err != nil {
return fmt.Errorf("failed to read contract creation bits: %w", err) return fmt.Errorf("failed to decode protected bits: %w", err)
} }
contractCreationBits := new(big.Int) btx.protectedBits = 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
} }
...@@ -105,27 +85,19 @@ func (btx *spanBatchTxs) contractCreationCount() (uint64, error) { ...@@ -105,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
} }
...@@ -183,37 +155,6 @@ func (btx *spanBatchTxs) encodeTxDatas(w io.Writer) error { ...@@ -183,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
...@@ -290,6 +231,9 @@ func (btx *spanBatchTxs) decodeTxDatas(r *bytes.Reader) error { ...@@ -290,6 +231,9 @@ func (btx *spanBatchTxs) decodeTxDatas(r *bytes.Reader) error {
} }
txDatas = append(txDatas, txData) txDatas = append(txDatas, txData)
txTypes = append(txTypes, txType) txTypes = append(txTypes, txType)
if txType == types.LegacyTxType {
btx.totalLegacyTxCount++
}
} }
btx.txDatas = txDatas btx.txDatas = txDatas
btx.txTypes = txTypes btx.txTypes = txTypes
...@@ -300,13 +244,23 @@ func (btx *spanBatchTxs) recoverV(chainID *big.Int) error { ...@@ -300,13 +244,23 @@ func (btx *spanBatchTxs) recoverV(chainID *big.Int) error {
if len(btx.txTypes) != len(btx.txSigs) { if len(btx.txTypes) != len(btx.txSigs) {
return errors.New("tx type length and tx sigs length mismatch") return errors.New("tx type length and tx sigs length mismatch")
} }
if btx.protectedBits == nil {
return errors.New("dev error: protected bits not set")
}
protectedBitsIdx := 0
for idx, txType := range btx.txTypes { for idx, txType := range btx.txTypes {
bit := uint64(btx.yParityBits.Bit(idx)) bit := uint64(btx.yParityBits.Bit(idx))
var v uint64 var v uint64
switch txType { switch txType {
case types.LegacyTxType: case types.LegacyTxType:
// EIP155 protectedBit := btx.protectedBits.Bit(protectedBitsIdx)
v = chainID.Uint64()*2 + 35 + bit protectedBitsIdx++
if protectedBit == 0 {
v = 27 + bit
} else {
// EIP-155
v = chainID.Uint64()*2 + 35 + bit
}
case types.AccessListTxType: case types.AccessListTxType:
v = bit v = bit
case types.DynamicFeeTxType: case types.DynamicFeeTxType:
...@@ -341,6 +295,9 @@ func (btx *spanBatchTxs) encode(w io.Writer) error { ...@@ -341,6 +295,9 @@ func (btx *spanBatchTxs) encode(w io.Writer) error {
if err := btx.encodeTxGases(w); err != nil { if err := btx.encodeTxGases(w); err != nil {
return err return err
} }
if err := btx.encodeProtectedBits(w); err != nil {
return err
}
return nil return nil
} }
...@@ -366,6 +323,9 @@ func (btx *spanBatchTxs) decode(r *bytes.Reader) error { ...@@ -366,6 +323,9 @@ func (btx *spanBatchTxs) decode(r *bytes.Reader) error {
if err := btx.decodeTxGases(r); err != nil { if err := btx.decodeTxGases(r); err != nil {
return err return err
} }
if err := btx.decodeProtectedBits(r); err != nil {
return err
}
return nil return nil
} }
...@@ -408,9 +368,14 @@ func convertVToYParity(v uint64, txType int) (uint, error) { ...@@ -408,9 +368,14 @@ func convertVToYParity(v uint64, txType int) (uint, error) {
var yParityBit uint var yParityBit uint
switch txType { switch txType {
case types.LegacyTxType: case types.LegacyTxType:
// EIP155: v = 2 * chainID + 35 + yParity if isProtectedV(v, txType) {
// v - 35 = yParity (mod 2) // EIP-155: v = 2 * chainID + 35 + yParity
yParityBit = uint((v - 35) & 1) // v - 35 = yParity (mod 2)
yParityBit = uint((v - 35) & 1)
} else {
// unprotected legacy txs must have v = 27 or 28
yParityBit = uint(v - 27)
}
case types.AccessListTxType: case types.AccessListTxType:
yParityBit = uint(v) yParityBit = uint(v)
case types.DynamicFeeTxType: case types.DynamicFeeTxType:
...@@ -421,6 +386,15 @@ func convertVToYParity(v uint64, txType int) (uint, error) { ...@@ -421,6 +386,15 @@ func convertVToYParity(v uint64, txType int) (uint, error) {
return yParityBit, nil return yParityBit, nil
} }
func isProtectedV(v uint64, txType int) bool {
if txType == types.LegacyTxType {
// if EIP-155 applied, v = 2 * chainID + 35 + yParity
return v != 27 && v != 28
}
// every non legacy tx are protected
return true
}
func newSpanBatchTxs(txs [][]byte, chainID *big.Int) (*spanBatchTxs, error) { func newSpanBatchTxs(txs [][]byte, chainID *big.Int) (*spanBatchTxs, error) {
totalBlockTxCount := uint64(len(txs)) totalBlockTxCount := uint64(len(txs))
var txSigs []spanBatchSignature var txSigs []spanBatchSignature
...@@ -431,11 +405,21 @@ func newSpanBatchTxs(txs [][]byte, chainID *big.Int) (*spanBatchTxs, error) { ...@@ -431,11 +405,21 @@ func newSpanBatchTxs(txs [][]byte, chainID *big.Int) (*spanBatchTxs, error) {
var txTypes []int var txTypes []int
contractCreationBits := new(big.Int) contractCreationBits := new(big.Int)
yParityBits := new(big.Int) yParityBits := new(big.Int)
protectedBits := new(big.Int)
totalLegacyTxCount := uint64(0)
for idx := 0; idx < int(totalBlockTxCount); idx++ { for idx := 0; idx < int(totalBlockTxCount); idx++ {
var tx types.Transaction var tx types.Transaction
if err := tx.UnmarshalBinary(txs[idx]); err != nil { if err := tx.UnmarshalBinary(txs[idx]); err != nil {
return nil, errors.New("failed to decode tx") return nil, errors.New("failed to decode tx")
} }
if tx.Type() == types.LegacyTxType {
protectedBit := uint(0)
if tx.Protected() {
protectedBit = uint(1)
}
protectedBits.SetBit(protectedBits, int(totalLegacyTxCount), protectedBit)
totalLegacyTxCount++
}
if tx.Protected() && tx.ChainId().Cmp(chainID) != 0 { if tx.Protected() && tx.ChainId().Cmp(chainID) != 0 {
return nil, fmt.Errorf("protected tx has chain ID %d, but expected chain ID %d", tx.ChainId(), chainID) return nil, fmt.Errorf("protected tx has chain ID %d, but expected chain ID %d", tx.ChainId(), chainID)
} }
...@@ -481,5 +465,7 @@ func newSpanBatchTxs(txs [][]byte, chainID *big.Int) (*spanBatchTxs, error) { ...@@ -481,5 +465,7 @@ func newSpanBatchTxs(txs [][]byte, chainID *big.Int) (*spanBatchTxs, error) {
txTos: txTos, txTos: txTos,
txDatas: txDatas, txDatas: txDatas,
txTypes: txTypes, txTypes: txTypes,
protectedBits: protectedBits,
totalLegacyTxCount: totalLegacyTxCount,
}, nil }, nil
} }
...@@ -14,6 +14,12 @@ import ( ...@@ -14,6 +14,12 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
) )
type txTypeTest struct {
name string
mkTx func(rng *rand.Rand, signer types.Signer) *types.Transaction
signer types.Signer
}
func TestSpanBatchTxsContractCreationBits(t *testing.T) { func TestSpanBatchTxsContractCreationBits(t *testing.T) {
rng := rand.New(rand.NewSource(0x1234567)) rng := rand.New(rand.NewSource(0x1234567))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
...@@ -112,6 +118,44 @@ func TestSpanBatchTxsYParityBits(t *testing.T) { ...@@ -112,6 +118,44 @@ func TestSpanBatchTxsYParityBits(t *testing.T) {
require.Equal(t, yParityBits, sbt.yParityBits) require.Equal(t, yParityBits, sbt.yParityBits)
} }
func TestSpanBatchTxsProtectedBits(t *testing.T) {
rng := rand.New(rand.NewSource(0x7331))
chainID := big.NewInt(rng.Int63n(1000))
rawSpanBatch := RandomRawSpanBatch(rng, chainID)
protectedBits := rawSpanBatch.txs.protectedBits
txTypes := rawSpanBatch.txs.txTypes
totalBlockTxCount := rawSpanBatch.txs.totalBlockTxCount
totalLegacyTxCount := rawSpanBatch.txs.totalLegacyTxCount
var sbt spanBatchTxs
sbt.protectedBits = protectedBits
sbt.totalBlockTxCount = totalBlockTxCount
sbt.txTypes = txTypes
sbt.totalLegacyTxCount = totalLegacyTxCount
var buf bytes.Buffer
err := sbt.encodeProtectedBits(&buf)
require.NoError(t, err)
// protectedBit field is fixed length: single bit
protectedBitBufferLen := totalLegacyTxCount / 8
require.NoError(t, err)
if totalLegacyTxCount%8 != 0 {
protectedBitBufferLen++
}
require.Equal(t, buf.Len(), int(protectedBitBufferLen))
result := buf.Bytes()
sbt.protectedBits = nil
r := bytes.NewReader(result)
err = sbt.decodeProtectedBits(r)
require.NoError(t, err)
require.Equal(t, protectedBits, sbt.protectedBits)
}
func TestSpanBatchTxsTxSigs(t *testing.T) { func TestSpanBatchTxsTxSigs(t *testing.T) {
rng := rand.New(rand.NewSource(0x73311337)) rng := rand.New(rand.NewSource(0x73311337))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
...@@ -263,42 +307,64 @@ func TestSpanBatchTxsRecoverV(t *testing.T) { ...@@ -263,42 +307,64 @@ func TestSpanBatchTxsRecoverV(t *testing.T) {
rng := rand.New(rand.NewSource(0x123)) rng := rand.New(rand.NewSource(0x123))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
signer := types.NewLondonSigner(chainID) londonSigner := types.NewLondonSigner(chainID)
totalblockTxCount := rng.Intn(100) totalblockTxCount := 20 + rng.Intn(100)
var spanBatchTxs spanBatchTxs cases := []txTypeTest{
var txTypes []int {"unprotected legacy tx", testutils.RandomLegacyTx, types.HomesteadSigner{}},
var txSigs []spanBatchSignature {"legacy tx", testutils.RandomLegacyTx, londonSigner},
var originalVs []uint64 {"access list tx", testutils.RandomAccessListTx, londonSigner},
yParityBits := new(big.Int) {"dynamic fee tx", testutils.RandomDynamicFeeTx, londonSigner},
for idx := 0; idx < totalblockTxCount; idx++ {
tx := testutils.RandomTx(rng, new(big.Int).SetUint64(rng.Uint64()), signer)
txTypes = append(txTypes, int(tx.Type()))
var txSig spanBatchSignature
v, r, s := tx.RawSignatureValues()
// Do not fill in txSig.V
txSig.r, _ = uint256.FromBig(r)
txSig.s, _ = uint256.FromBig(s)
txSigs = append(txSigs, txSig)
originalVs = append(originalVs, v.Uint64())
yParityBit, err := convertVToYParity(v.Uint64(), int(tx.Type()))
require.NoError(t, err)
yParityBits.SetBit(yParityBits, idx, yParityBit)
} }
spanBatchTxs.yParityBits = yParityBits for _, testCase := range cases {
spanBatchTxs.txSigs = txSigs t.Run(testCase.name, func(t *testing.T) {
spanBatchTxs.txTypes = txTypes var spanBatchTxs spanBatchTxs
// recover txSig.v var txTypes []int
err := spanBatchTxs.recoverV(chainID) var txSigs []spanBatchSignature
require.NoError(t, err) var originalVs []uint64
yParityBits := new(big.Int)
protectedBits := new(big.Int)
totalLegacyTxCount := 0
for idx := 0; idx < totalblockTxCount; idx++ {
tx := testCase.mkTx(rng, testCase.signer)
txType := tx.Type()
txTypes = append(txTypes, int(txType))
var txSig spanBatchSignature
v, r, s := tx.RawSignatureValues()
if txType == types.LegacyTxType {
protectedBit := uint(0)
if tx.Protected() {
protectedBit = uint(1)
}
protectedBits.SetBit(protectedBits, int(totalLegacyTxCount), protectedBit)
totalLegacyTxCount++
}
// Do not fill in txSig.V
txSig.r, _ = uint256.FromBig(r)
txSig.s, _ = uint256.FromBig(s)
txSigs = append(txSigs, txSig)
originalVs = append(originalVs, v.Uint64())
yParityBit, err := convertVToYParity(v.Uint64(), int(tx.Type()))
require.NoError(t, err)
yParityBits.SetBit(yParityBits, idx, yParityBit)
}
spanBatchTxs.yParityBits = yParityBits
spanBatchTxs.txSigs = txSigs
spanBatchTxs.txTypes = txTypes
spanBatchTxs.protectedBits = protectedBits
// recover txSig.v
err := spanBatchTxs.recoverV(chainID)
require.NoError(t, err)
var recoveredVs []uint64 var recoveredVs []uint64
for _, txSig := range spanBatchTxs.txSigs { for _, txSig := range spanBatchTxs.txSigs {
recoveredVs = append(recoveredVs, txSig.v) recoveredVs = append(recoveredVs, txSig.v)
}
require.Equal(t, originalVs, recoveredVs, "recovered v mismatch")
})
} }
require.Equal(t, originalVs, recoveredVs, "recovered v mismatch")
} }
func TestSpanBatchTxsRoundTrip(t *testing.T) { func TestSpanBatchTxsRoundTrip(t *testing.T) {
...@@ -332,24 +398,35 @@ func TestSpanBatchTxsRoundTrip(t *testing.T) { ...@@ -332,24 +398,35 @@ func TestSpanBatchTxsRoundTrip(t *testing.T) {
func TestSpanBatchTxsRoundTripFullTxs(t *testing.T) { func TestSpanBatchTxsRoundTripFullTxs(t *testing.T) {
rng := rand.New(rand.NewSource(0x13377331)) rng := rand.New(rand.NewSource(0x13377331))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
signer := types.NewLondonSigner(chainID) londonSigner := types.NewLondonSigner(chainID)
for i := 0; i < 4; i++ { cases := []txTypeTest{
totalblockTxCounts := uint64(1 + rng.Int()&0xFF) {"unprotected legacy tx", testutils.RandomLegacyTx, types.HomesteadSigner{}},
var txs [][]byte {"legacy tx", testutils.RandomLegacyTx, londonSigner},
for i := 0; i < int(totalblockTxCounts); i++ { {"access list tx", testutils.RandomAccessListTx, londonSigner},
tx := testutils.RandomTx(rng, new(big.Int).SetUint64(rng.Uint64()), signer) {"dynamic fee tx", testutils.RandomDynamicFeeTx, londonSigner},
rawTx, err := tx.MarshalBinary() }
require.NoError(t, err)
txs = append(txs, rawTx)
}
sbt, err := newSpanBatchTxs(txs, chainID)
require.NoError(t, err)
txs2, err := sbt.fullTxs(chainID)
require.NoError(t, err)
require.Equal(t, txs, txs2) for _, testCase := range cases {
t.Run(testCase.name, func(t *testing.T) {
for i := 0; i < 4; i++ {
totalblockTxCounts := uint64(1 + rng.Int()&0xFF)
var txs [][]byte
for i := 0; i < int(totalblockTxCounts); i++ {
tx := testCase.mkTx(rng, testCase.signer)
rawTx, err := tx.MarshalBinary()
require.NoError(t, err)
txs = append(txs, rawTx)
}
sbt, err := newSpanBatchTxs(txs, chainID)
require.NoError(t, err)
txs2, err := sbt.fullTxs(chainID)
require.NoError(t, err)
require.Equal(t, txs, txs2)
}
})
} }
} }
...@@ -362,6 +439,7 @@ func TestSpanBatchTxsRecoverVInvalidTxType(t *testing.T) { ...@@ -362,6 +439,7 @@ func TestSpanBatchTxsRecoverVInvalidTxType(t *testing.T) {
sbt.txTypes = []int{types.DepositTxType} sbt.txTypes = []int{types.DepositTxType}
sbt.txSigs = []spanBatchSignature{{v: 0, r: nil, s: nil}} sbt.txSigs = []spanBatchSignature{{v: 0, r: nil, s: nil}}
sbt.yParityBits = new(big.Int) sbt.yParityBits = new(big.Int)
sbt.protectedBits = new(big.Int)
err := sbt.recoverV(chainID) err := sbt.recoverV(chainID)
require.ErrorContains(t, err, "invalid tx type") require.ErrorContains(t, err, "invalid tx type")
...@@ -370,24 +448,35 @@ func TestSpanBatchTxsRecoverVInvalidTxType(t *testing.T) { ...@@ -370,24 +448,35 @@ func TestSpanBatchTxsRecoverVInvalidTxType(t *testing.T) {
func TestSpanBatchTxsFullTxNotEnoughTxTos(t *testing.T) { func TestSpanBatchTxsFullTxNotEnoughTxTos(t *testing.T) {
rng := rand.New(rand.NewSource(0x13572468)) rng := rand.New(rand.NewSource(0x13572468))
chainID := big.NewInt(rng.Int63n(1000)) chainID := big.NewInt(rng.Int63n(1000))
signer := types.NewLondonSigner(chainID) londonSigner := types.NewLondonSigner(chainID)
totalblockTxCounts := uint64(1 + rng.Int()&0xFF) cases := []txTypeTest{
var txs [][]byte {"unprotected legacy tx", testutils.RandomLegacyTx, types.HomesteadSigner{}},
for i := 0; i < int(totalblockTxCounts); i++ { {"legacy tx", testutils.RandomLegacyTx, londonSigner},
tx := testutils.RandomTx(rng, new(big.Int).SetUint64(rng.Uint64()), signer) {"access list tx", testutils.RandomAccessListTx, londonSigner},
rawTx, err := tx.MarshalBinary() {"dynamic fee tx", testutils.RandomDynamicFeeTx, londonSigner},
require.NoError(t, err)
txs = append(txs, rawTx)
} }
sbt, err := newSpanBatchTxs(txs, chainID)
require.NoError(t, err)
// drop single to field for _, testCase := range cases {
sbt.txTos = sbt.txTos[:len(sbt.txTos)-2] t.Run(testCase.name, func(t *testing.T) {
totalblockTxCounts := uint64(1 + rng.Int()&0xFF)
var txs [][]byte
for i := 0; i < int(totalblockTxCounts); i++ {
tx := testCase.mkTx(rng, testCase.signer)
rawTx, err := tx.MarshalBinary()
require.NoError(t, err)
txs = append(txs, rawTx)
}
sbt, err := newSpanBatchTxs(txs, chainID)
require.NoError(t, err)
// drop single to field
sbt.txTos = sbt.txTos[:len(sbt.txTos)-2]
_, err = sbt.fullTxs(chainID) _, err = sbt.fullTxs(chainID)
require.EqualError(t, err, "tx to not enough") require.EqualError(t, err, "tx to not enough")
})
}
} }
func TestSpanBatchTxsMaxContractCreationBitsLength(t *testing.T) { func TestSpanBatchTxsMaxContractCreationBitsLength(t *testing.T) {
...@@ -407,3 +496,13 @@ func TestSpanBatchTxsMaxYParityBitsLength(t *testing.T) { ...@@ -407,3 +496,13 @@ func TestSpanBatchTxsMaxYParityBitsLength(t *testing.T) {
err := sb.decodeOriginBits(r) err := sb.decodeOriginBits(r)
require.ErrorIs(t, err, ErrTooBigSpanBatchSize) require.ErrorIs(t, err, ErrTooBigSpanBatchSize)
} }
func TestSpanBatchTxsMaxProtectedBitsLength(t *testing.T) {
var sb RawSpanBatch
sb.txs = &spanBatchTxs{}
sb.txs.totalLegacyTxCount = 0xFFFFFFFFFFFFFFFF
r := bytes.NewReader([]byte{})
err := sb.txs.decodeProtectedBits(r)
require.ErrorIs(t, err, ErrTooBigSpanBatchSize)
}
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
}
...@@ -157,6 +157,10 @@ func RandomTx(rng *rand.Rand, baseFee *big.Int, signer types.Signer) *types.Tran ...@@ -157,6 +157,10 @@ func RandomTx(rng *rand.Rand, baseFee *big.Int, signer types.Signer) *types.Tran
return tx return tx
} }
func RandomLegacyTxNotProtected(rng *rand.Rand) *types.Transaction {
return RandomLegacyTx(rng, types.HomesteadSigner{})
}
func RandomLegacyTx(rng *rand.Rand, signer types.Signer) *types.Transaction { func RandomLegacyTx(rng *rand.Rand, signer types.Signer) *types.Transaction {
key := InsecureRandomKey(rng) key := InsecureRandomKey(rng)
txData := &types.LegacyTx{ txData := &types.LegacyTx{
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
- [`Chain ID` removal from initial specs](#chain-id-removal-from-initial-specs) - [`Chain ID` removal from initial specs](#chain-id-removal-from-initial-specs)
- [Reorganization of constant length transaction fields](#reorganization-of-constant-length-transaction-fields) - [Reorganization of constant length transaction fields](#reorganization-of-constant-length-transaction-fields)
- [RLP encoding for only variable length fields](#rlp-encoding-for-only-variable-length-fields) - [RLP encoding for only variable length fields](#rlp-encoding-for-only-variable-length-fields)
- [Store `y_parity` instead of `v`](#store-y_parity-instead-of-v) - [Store `y_parity` and `protected_bit` instead of `v`](#store-y_parity-and-protected_bit-instead-of-v)
- [Adjust `txs` Data Layout for Better Compression](#adjust-txs-data-layout-for-better-compression) - [Adjust `txs` Data Layout for Better Compression](#adjust-txs-data-layout-for-better-compression)
- [`fee_recipients` Encoding Scheme](#fee_recipients-encoding-scheme) - [`fee_recipients` Encoding Scheme](#fee_recipients-encoding-scheme)
- [How derivation works with Span Batch?](#how-derivation-works-with-span-batch) - [How derivation works with Span Batch?](#how-derivation-works-with-span-batch)
...@@ -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,14 +101,15 @@ Where: ...@@ -98,14 +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 ++ tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases` - `txs = contract_creation_bits ++ y_parity_bits ++
- `contract_creation_bits`: bit list of `sum(block_tx_counts)` bits, right-padded to a multiple of 8 bits, tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_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`
...@@ -121,6 +125,8 @@ Where: ...@@ -121,6 +125,8 @@ 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`: standard bitlist of length of number of legacy transactions:
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:
...@@ -147,6 +153,8 @@ Where: ...@@ -147,6 +153,8 @@ Where:
[EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
[EIP-155]: https://eips.ethereum.org/EIPS/eip-155
Total size of encoded span batch is limited to `MAX_SPAN_BATCH_SIZE` (currently 10,000,000 bytes, Total size of encoded span batch is limited to `MAX_SPAN_BATCH_SIZE` (currently 10,000,000 bytes,
equal to `MAX_RLP_BYTES_PER_CHANNEL`). Therefore every field size of span batch will be implicitly limited to equal to `MAX_RLP_BYTES_PER_CHANNEL`). Therefore every field size of span batch will be implicitly limited to
`MAX_SPAN_BATCH_SIZE` . There can be at least single span batch per channel, and channel size is limited `MAX_SPAN_BATCH_SIZE` . There can be at least single span batch per channel, and channel size is limited
...@@ -201,10 +209,12 @@ Our goal is to find the sweet spot on code complexity - span batch size tradeoff ...@@ -201,10 +209,12 @@ Our goal is to find the sweet spot on code complexity - span batch size tradeoff
I decided that using RLP for all variable length fields will be the best option, I decided that using RLP for all variable length fields will be the best option,
not risking codebase with gnarly custom encoding/decoding implementations. not risking codebase with gnarly custom encoding/decoding implementations.
### Store `y_parity` instead of `v` ### Store `y_parity` and `protected_bit` instead of `v`
For legacy type transactions, `v = 2 * ChainID + y_parity`. For other types of transactions, `v = y_parity`. Only legacy type transactions can be optionally protected. If protected([EIP-155]), `v = 2 * ChainID + 35 + y_parity`.
We may only store `y_parity`, which is single bit per L2 transaction. Else, `v = 27 + y_parity`. For other types of transactions, `v = y_parity`.
We store `y_parity`, which is single bit per L2 transaction.
We store `protected_bit`, which is single bit per L2 legacy type transactions to indicate that tx is protected.
This optimization will benefit more when ratio between number of legacy type transactions over number of transactions This optimization will benefit more when ratio between number of legacy type transactions over number of transactions
excluding deposit tx is higher. excluding deposit tx is higher.
......
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