Skip to content

Commit

Permalink
Merge pull request ethereum-optimism#246 from ethereum-optimism/jg/ba…
Browse files Browse the repository at this point in the history
…tch_struct

ref impl: Add simple batch serialization
  • Loading branch information
protolambda authored Mar 16, 2022
2 parents 55dc76c + 67ca957 commit 49b0ab4
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 15 deletions.
153 changes: 153 additions & 0 deletions opnode/rollup/derive/batch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package derive

import (
"bytes"
"errors"
"fmt"
"io"
"sync"

"github.com/ethereum-optimism/optimistic-specs/opnode/rollup"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
)

// Batch format
// first byte is type followed by bytestring.
//
// BatchV1Type := 0
// batchV1 := BatchV1Type ++ RLP([epoch, timestamp, transaction_list]
//
// An empty input is not a valid batch.
//
// Batch-bundle format
// first byte is type followed by bytestring
//
// payload := RLP([batch_0, batch_1, ..., batch_N])
// bundleV1 := BatchBundleV1Type ++ payload
// bundleV2 := BatchBundleV2Type ++ compress(payload) # TODO: compressed bundle of batches
//
// An empty input is not a valid bundle.
//
// Note: the type system is based on L1 typed transactions.

// encodeBufferPool holds temporary encoder buffers for batch encoding
var encodeBufferPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}

const (
BatchV1Type = iota
)

const (
BatchBundleV1Type = iota
BatchBundleV2Type
)

type BatchV1 struct {
Epoch rollup.Epoch // aka l1 num
Timestamp uint64
// no feeRecipient address input, all fees go to a L2 contract
Transactions []hexutil.Bytes
}

type BatchData struct {
BatchV1
// batches may contain additional data with new upgrades
}

func DecodeBatches(config *rollup.Config, r io.Reader) ([]*BatchData, error) {
var typeData [1]byte
if _, err := io.ReadFull(r, typeData[:]); err != nil {
return nil, fmt.Errorf("failed to read batch bundle type byte: %v", err)
}
switch typeData[0] {
case BatchBundleV1Type:
var out []*BatchData
if err := rlp.Decode(r, &out); err != nil {
return nil, fmt.Errorf("failed to decode v1 batches list: %v", err)
}
return out, nil
case BatchBundleV2Type:
// TODO: implement compression of a bundle of batches
return nil, errors.New("bundle v2 not supported yet")
default:
return nil, fmt.Errorf("unrecognized batch bundle type: %d", typeData[0])
}
}

func EncodeBatches(config *rollup.Config, batches []*BatchData, w io.Writer) error {
// default to encode as v1 (no compression). Config may change this in the future.
bundleType := byte(BatchBundleV1Type)

if _, err := w.Write([]byte{bundleType}); err != nil {
return fmt.Errorf("failed to encode batch type")
}
switch bundleType {
case BatchBundleV1Type:
if err := rlp.Encode(w, batches); err != nil {
return fmt.Errorf("failed to encode RLP-list payload of v1 bundle: %v", err)
}
return nil
case BatchBundleV2Type:
return errors.New("bundle v2 not supported yet")
default:
return fmt.Errorf("unrecognized batch bundle type: %d", bundleType)
}
}

// EncodeRLP implements rlp.Encoder
func (b *BatchData) EncodeRLP(w io.Writer) error {
buf := encodeBufferPool.Get().(*bytes.Buffer)
defer encodeBufferPool.Put(buf)
buf.Reset()
if err := b.encodeTyped(buf); err != nil {
return err
}
return rlp.Encode(w, buf.Bytes())
}

// MarshalBinary returns the canonical encoding of the batch.
func (b *BatchData) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
err := b.encodeTyped(&buf)
return buf.Bytes(), err
}

func (b *BatchData) encodeTyped(buf *bytes.Buffer) error {
buf.WriteByte(BatchV1Type)
return rlp.Encode(buf, &b.BatchV1)
}

// DecodeRLP implements rlp.Decoder
func (b *BatchData) DecodeRLP(s *rlp.Stream) error {
if b == nil {
return errors.New("cannot decode into nil BatchData")
}
v, err := s.Bytes()
if err != nil {
return err
}
return b.decodeTyped(v)
}

// UnmarshalBinary decodes the canonical encoding of batch.
func (b *BatchData) UnmarshalBinary(data []byte) error {
if b == nil {
return errors.New("cannot decode into nil BatchData")
}
return b.decodeTyped(data)
}

func (b *BatchData) decodeTyped(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("batch too short")
}
switch data[0] {
case BatchV1Type:
return rlp.DecodeBytes(data[1:], &b.BatchV1)
default:
return fmt.Errorf("unrecognized batch type: %d", data[0])
}
}
45 changes: 45 additions & 0 deletions opnode/rollup/derive/batch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package derive

import (
"bytes"
"testing"

"github.com/ethereum-optimism/optimistic-specs/opnode/rollup"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/assert"
)

func TestBatchRoundTrip(t *testing.T) {
batches := []*BatchData{
{
BatchV1: BatchV1{
Epoch: 0,
Timestamp: 0,
Transactions: []hexutil.Bytes{},
},
},
{
BatchV1: BatchV1{
Epoch: 1,
Timestamp: 1647026951,
Transactions: []hexutil.Bytes{[]byte{0, 0, 0}, []byte{0x76, 0xfd, 0x7c}},
},
},
}

for i, batch := range batches {
enc, err := batch.MarshalBinary()
assert.NoError(t, err)
var dec BatchData
err = dec.UnmarshalBinary(enc)
assert.NoError(t, err)
assert.Equal(t, batch, &dec, "Batch not equal test case %v", i)
}
var buf bytes.Buffer
err := EncodeBatches(&rollup.Config{}, batches, &buf)
assert.NoError(t, err)
out, err := DecodeBatches(&rollup.Config{}, &buf)
assert.NoError(t, err)
assert.Equal(t, batches, out)
}
26 changes: 11 additions & 15 deletions opnode/rollup/derive/payload_attributes.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package derive

import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
Expand Down Expand Up @@ -180,25 +181,31 @@ func UserDeposits(height uint64, receipts []*types.Receipt) ([]*types.DepositTx,
return out, nil
}

func BatchesFromEVMTransactions(config *rollup.Config, txs []*types.Transaction) (out []BatchData) {
func BatchesFromEVMTransactions(config *rollup.Config, txs []*types.Transaction) (out []*BatchData) {
l1Signer := config.L1Signer()
for _, tx := range txs {
if to := tx.To(); to != nil && *to == config.BatchInboxAddress {
seqDataSubmitter, err := l1Signer.Sender(tx)
if err != nil {
// TODO: log error
continue // bad signature, ignore
}
// some random L1 user might have sent a transaction to our batch inbox, ignore them
if seqDataSubmitter != config.BatchSenderAddress {
continue // not an authorized batch submitter, ignore
}
out = append(out, ParseBatches(tx.Data())...)
batches, err := DecodeBatches(config, bytes.NewReader(tx.Data()))
if err != nil {
// TODO: log error
continue
}
out = append(out, batches...)
}
}
return
}

func FilterBatches(config *rollup.Config, epoch rollup.Epoch, minL2Time uint64, maxL2Time uint64, batches []BatchData) (out []BatchData) {
func FilterBatches(config *rollup.Config, epoch rollup.Epoch, minL2Time uint64, maxL2Time uint64, batches []*BatchData) (out []*BatchData) {
uniqueTime := make(map[uint64]struct{})
for _, batch := range batches {
if batch.Epoch != epoch {
Expand Down Expand Up @@ -238,7 +245,7 @@ type L2Info interface {
// - The L2 information of the block the new derived blocks build on
//
// This is a pure function.
func PayloadAttributes(config *rollup.Config, l1Info L1Info, receipts []*types.Receipt, seqWindow []BatchData, l2Info L2Info) ([]*l2.PayloadAttributes, error) {
func PayloadAttributes(config *rollup.Config, l1Info L1Info, receipts []*types.Receipt, seqWindow []*BatchData, l2Info L2Info) ([]*l2.PayloadAttributes, error) {
// Retrieve the deposits of this epoch (all deposits from the first block)
deposits, err := DeriveDeposits(l1Info, receipts)
if err != nil {
Expand Down Expand Up @@ -328,14 +335,3 @@ func DeriveDeposits(l1Info L1Info, receipts []*types.Receipt) ([]l2.Data, error)
}
return encodedTxs, nil
}

type BatchData struct {
Epoch rollup.Epoch // aka l1 num
Timestamp uint64
// no feeRecipient address input, all fees go to a L2 contract
Transactions []l2.Data
}

func ParseBatches(data l2.Data) []BatchData {
return nil // TODO
}

0 comments on commit 49b0ab4

Please sign in to comment.