Skip to content

Commit

Permalink
EncodeBatchV2 allow to set Tx in binary (0xPolygonHermez#3143)
Browse files Browse the repository at this point in the history
* EncodeBatchV2 allow to set Tx in binary
* actions/setup-go to v5 to avoid use node 16
  • Loading branch information
joanestebanr authored Jan 29, 2024
1 parent 7497d07 commit fd05d50
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 97 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/jsonschema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 #v3
uses: actions/checkout@v4
# https://github.com/actions/checkout#Checkout-pull-request-HEAD-commit-instead-of-merge-commit
# Checkout pull request HEAD commit instead of merge commit
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.21.x
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
uses: actions/checkout@v4
- name: Lint
run: |
make install-linter
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: 1.21

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-from-prover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 #v3
uses: actions/checkout@v4
with:
repository: 0xPolygonHermez/zkevm-node

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-full-non-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
env:
Expand Down
11 changes: 11 additions & 0 deletions state/batchV2.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,14 @@ func (s *State) ProcessAndStoreClosedBatchV2(ctx context.Context, processingCtx
BatchL2Data: *BatchL2Data,
}, dbTx)
}

// BuildChangeL2Block returns a changeL2Block tx to use in the BatchL2Data
func (p *State) BuildChangeL2Block(deltaTimestamp uint32, l1InfoTreeIndex uint32) []byte {
l2block := ChangeL2BlockHeader{
DeltaTimestamp: deltaTimestamp,
IndexL1InfoTree: l1InfoTreeIndex,
}
var data []byte
data = l2block.Encode(data)
return data
}
166 changes: 116 additions & 50 deletions state/encoding_batch_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
This file provide functions to work with ETROG batches:
- EncodeBatchV2 (equivalent to EncodeTransactions)
- DecodeBatchV2 (equivalent to DecodeTxs)
- DecodeForcedBatchV2)
- DecodeForcedBatchV2
Also provide a builder class to create batches (BatchV2Encoder):
This method doesnt check anything, so is more flexible but you need to know what you are doing
- `builder := NewBatchV2Encoder()` : Create a new `BatchV2Encoder``
- You can call to `AddBlockHeader` or `AddTransaction` to add a block header or a transaction as you wish
- You can call to `GetResult` to get the batch data
// batch data format:
Expand All @@ -27,11 +33,25 @@ This file provide functions to work with ETROG batches:
// 0x00 | 32 | V
// 0x00 | 1 | efficiencyPercentage
// Repeat Transaction
//
// Usage:
// There are 2 ways of use this module, direct calls or a builder class:
// 1) Direct calls:
// - EncodeBatchV2: Encode a batch of transactions
// - DecodeBatchV2: Decode a batch of transactions
//
// 2) Builder class:
// This method doesnt check anything, so is more flexible but you need to know what you are doing
// - builder := NewBatchV2Encoder(): Create a new BatchV2Encoder
// - You can call to `AddBlockHeader` or `AddTransaction` to add a block header or a transaction as you wish
// - You can call to `GetResult` to get the batch data
*/

package state

import (
"encoding/binary"
"errors"
"fmt"
"strconv"
Expand All @@ -42,11 +62,16 @@ import (
"github.com/ethereum/go-ethereum/rlp"
)

// L2BlockRaw is the raw representation of a L2 block.
type L2BlockRaw struct {
// ChangeL2BlockHeader is the header of a L2 block.
type ChangeL2BlockHeader struct {
DeltaTimestamp uint32
IndexL1InfoTree uint32
Transactions []L2TxRaw
}

// L2BlockRaw is the raw representation of a L2 block.
type L2BlockRaw struct {
ChangeL2BlockHeader
Transactions []L2TxRaw
}

// BatchRawV2 is the representation of a batch of transactions.
Expand All @@ -61,12 +86,15 @@ type ForcedBatchRawV2 struct {

// L2TxRaw is the raw representation of a L2 transaction inside a L2 block.
type L2TxRaw struct {
Tx types.Transaction
EfficiencyPercentage uint8
EfficiencyPercentage uint8 // valid always
TxAlreadyEncoded bool // If true the tx is already encoded (data field is used)
Tx types.Transaction // valid if TxAlreadyEncoded == false
Data []byte // valid if TxAlreadyEncoded == true
}

const (
changeL2Block = uint8(0x0b)
sizeUInt32 = 4
)

var (
Expand All @@ -92,57 +120,88 @@ func (b *BatchRawV2) String() string {

// EncodeBatchV2 encodes a batch of transactions into a byte slice.
func EncodeBatchV2(batch *BatchRawV2) ([]byte, error) {
var err error
var batchData []byte
if batch == nil {
return nil, fmt.Errorf("batch is nil: %w", ErrInvalidBatchV2)
}
blocks := batch.Blocks
if len(blocks) == 0 {
if len(batch.Blocks) == 0 {
return nil, fmt.Errorf("a batch need minimum a L2Block: %w", ErrInvalidBatchV2)
}
for _, block := range blocks {
batchData, err = EncodeBlockHeaderV2(batchData, block)

encoder := NewBatchV2Encoder()
for _, block := range batch.Blocks {
encoder.AddBlockHeader(block.ChangeL2BlockHeader)
err := encoder.AddTransactions(block.Transactions)
if err != nil {
return nil, fmt.Errorf("can't encode block header: %w", err)
return nil, fmt.Errorf("can't encode tx: %w", err)
}
for _, tx := range block.Transactions {
batchData, err = encodeTxRLP(batchData, tx)
if err != nil {
return nil, fmt.Errorf("can't encode tx: %w", err)
}
}
return encoder.GetResult(), nil
}

// BatchV2Encoder is a builder of the batchl2data used by EncodeBatchV2
type BatchV2Encoder struct {
batchData []byte
}

// NewBatchV2Encoder creates a new BatchV2Encoder.
func NewBatchV2Encoder() *BatchV2Encoder {
return &BatchV2Encoder{}
}

// AddBlockHeader adds a block header to the batch.
func (b *BatchV2Encoder) AddBlockHeader(l2BlockHeader ChangeL2BlockHeader) {
b.batchData = l2BlockHeader.Encode(b.batchData)
}

// AddTransactions adds a set of transactions to the batch.
func (b *BatchV2Encoder) AddTransactions(transactions []L2TxRaw) error {
for _, tx := range transactions {
err := b.AddTransaction(tx)
if err != nil {
return fmt.Errorf("can't encode tx: %w", err)
}
}
return batchData, nil
return nil
}

// AddTransaction adds a transaction to the batch.
func (b *BatchV2Encoder) AddTransaction(transaction L2TxRaw) error {
var err error
b.batchData, err = transaction.Encode(b.batchData)
if err != nil {
return fmt.Errorf("can't encode tx: %w", err)
}
return nil
}

// EncodeBlockHeaderV2 encodes a batch of l2blocks header into a byte slice.
func EncodeBlockHeaderV2(batchData []byte, block L2BlockRaw) ([]byte, error) {
// GetResult returns the batch data.
func (b *BatchV2Encoder) GetResult() []byte {
return b.batchData
}

// Encode encodes a batch of l2blocks header into a byte slice.
func (c ChangeL2BlockHeader) Encode(batchData []byte) []byte {
batchData = append(batchData, changeL2Block)
batchData = append(batchData, serializeUint32(block.DeltaTimestamp)...)
batchData = append(batchData, serializeUint32(block.IndexL1InfoTree)...)
return batchData, nil
batchData = append(batchData, encodeUint32(c.DeltaTimestamp)...)
batchData = append(batchData, encodeUint32(c.IndexL1InfoTree)...)
return batchData
}

func encodeTxRLP(batchData []byte, tx L2TxRaw) ([]byte, error) {
rlpTx, err := prepareRPLTxData(tx.Tx)
if err != nil {
return nil, fmt.Errorf("can't encode tx to RLP: %w", err)
// Encode encodes a transaction into a byte slice.
func (tx L2TxRaw) Encode(batchData []byte) ([]byte, error) {
if tx.TxAlreadyEncoded {
batchData = append(batchData, tx.Data...)
} else {
rlpTx, err := prepareRLPTxData(tx.Tx)
if err != nil {
return nil, fmt.Errorf("can't encode tx to RLP: %w", err)
}
batchData = append(batchData, rlpTx...)
}
batchData = append(batchData, rlpTx...)
batchData = append(batchData, tx.EfficiencyPercentage)
return batchData, nil
}

func serializeUint32(value uint32) []byte {
return []byte{
byte(value >> 24), // nolint:gomnd
byte(value >> 16), // nolint:gomnd
byte(value >> 8), // nolint:gomnd
byte(value),
} // nolint:gomnd
}

// DecodeBatchV2 decodes a batch of transactions from a byte slice.
func DecodeBatchV2(txsData []byte) (*BatchRawV2, error) {
// The transactions is not RLP encoded. Is the raw bytes in this form: 1 byte for the transaction type (always 0b for changeL2Block) + 4 bytes for deltaTimestamp + for bytes for indexL1InfoTree
Expand All @@ -164,7 +223,7 @@ func DecodeBatchV2(txsData []byte) (*BatchRawV2, error) {
// is a tx
default:
if currentBlock == nil {
_, _, err := decodeTxRLP(txsData, pos)
_, _, err := DecodeTxRLP(txsData, pos)
if err == nil {
// There is no changeL2Block but have a valid RLP transaction
return nil, ErrBatchV2DontStartWithChangeL2Block
Expand All @@ -174,7 +233,7 @@ func DecodeBatchV2(txsData []byte) (*BatchRawV2, error) {
}
}
var tx *L2TxRaw
pos, tx, err = decodeTxRLP(txsData, pos)
pos, tx, err = DecodeTxRLP(txsData, pos)
if err != nil {
return nil, fmt.Errorf("can't decode transactions: %w", err)
}
Expand Down Expand Up @@ -215,19 +274,20 @@ func DecodeForcedBatchV2(txsData []byte) (*ForcedBatchRawV2, error) {
func decodeBlockHeader(txsData []byte, pos int) (int, *L2BlockRaw, error) {
var err error
currentBlock := &L2BlockRaw{}
pos, currentBlock.DeltaTimestamp, err = deserializeUint32(txsData, pos)
pos, currentBlock.DeltaTimestamp, err = decodeUint32(txsData, pos)
if err != nil {
return 0, nil, fmt.Errorf("can't get deltaTimestamp: %w", err)
}
pos, currentBlock.IndexL1InfoTree, err = deserializeUint32(txsData, pos)
pos, currentBlock.IndexL1InfoTree, err = decodeUint32(txsData, pos)
if err != nil {
return 0, nil, fmt.Errorf("can't get leafIndex: %w", err)
}

return pos, currentBlock, nil
}

func decodeTxRLP(txsData []byte, offset int) (int, *L2TxRaw, error) {
// DecodeTxRLP decodes a transaction from a byte slice.
func DecodeTxRLP(txsData []byte, offset int) (int, *L2TxRaw, error) {
var err error
length, err := decodeRLPListLengthFromOffset(txsData, offset)
if err != nil {
Expand Down Expand Up @@ -265,13 +325,6 @@ func decodeTxRLP(txsData []byte, offset int) (int, *L2TxRaw, error) {
return int(endPos), l2Tx, err
}

func deserializeUint32(txsData []byte, pos int) (int, uint32, error) {
if len(txsData)-pos < 4 { // nolint:gomnd
return 0, 0, fmt.Errorf("can't get u32 because not enough data: %w", ErrInvalidBatchV2)
}
return pos + 4, uint32(txsData[pos])<<24 | uint32(txsData[pos+1])<<16 | uint32(txsData[pos+2])<<8 | uint32(txsData[pos+3]), nil // nolint:gomnd
}

// It returns the length of data from the param offset
// ex:
// 0xc0 -> empty data -> 1 byte because it include the 0xc0
Expand Down Expand Up @@ -302,3 +355,16 @@ func decodeRLPListLengthFromOffset(txsData []byte, offset int) (uint64, error) {
}
return length + headerByteLength, nil
}

func encodeUint32(value uint32) []byte {
data := make([]byte, sizeUInt32)
binary.BigEndian.PutUint32(data, value)
return data
}

func decodeUint32(txsData []byte, pos int) (int, uint32, error) {
if len(txsData)-pos < sizeUInt32 {
return 0, 0, fmt.Errorf("can't get u32 because not enough data: %w", ErrInvalidBatchV2)
}
return pos + sizeUInt32, binary.BigEndian.Uint32(txsData[pos : pos+sizeUInt32]), nil
}
Loading

0 comments on commit fd05d50

Please sign in to comment.