Skip to content

Commit

Permalink
Regard the BlockJustification as a part of L2Msg (OffchainLabs#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
ImJeremyHe authored Dec 5, 2023
1 parent 5954ec3 commit 1868c43
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 91 deletions.
14 changes: 6 additions & 8 deletions arbos/arbostypes/incomingmessage.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,12 @@ type EspressoBlockJustification struct {
}

type L1IncomingMessageHeader struct {
Kind uint8 `json:"kind"`
Poster common.Address `json:"sender"`
BlockNumber uint64 `json:"blockNumber"`
Timestamp uint64 `json:"timestamp"`
RequestId *common.Hash `json:"requestId" rlp:"nilList"`
L1BaseFee *big.Int `json:"baseFeeL1"`
BlockJustification *EspressoBlockJustification `json:"justification,omitempty" rlp:"optional"`
Kind uint8 `json:"kind"`
Poster common.Address `json:"sender"`
BlockNumber uint64 `json:"blockNumber"`
Timestamp uint64 `json:"timestamp"`
RequestId *common.Hash `json:"requestId" rlp:"nilList"`
L1BaseFee *big.Int `json:"baseFeeL1"`
}

func (h L1IncomingMessageHeader) SeqNum() (uint64, error) {
Expand Down Expand Up @@ -235,7 +234,6 @@ func ParseIncomingL1Message(rd io.Reader, batchFetcher FallibleBatchFetcher) (*L
timestamp,
&requestId,
baseFeeL1.Big(),
nil,
},
data,
nil,
Expand Down
106 changes: 71 additions & 35 deletions arbos/parse_l2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package arbos

import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
Expand All @@ -22,12 +23,12 @@ import (

type InfallibleBatchFetcher func(batchNum uint64, batchHash common.Hash) []byte

func ParseEspressoTransactions(msg *arbostypes.L1IncomingMessage) ([]espresso.Bytes, error) {
func ParseEspressoMsg(msg *arbostypes.L1IncomingMessage) ([]espresso.Bytes, *arbostypes.EspressoBlockJustification, error) {
if msg.Header.Kind != arbostypes.L1MessageType_L2Message {
return nil, errors.New("Parsing espresso transactions failed. Invalid L1Message type")
return nil, nil, errors.New("Parsing espresso transactions failed. Invalid L1Message type")
}
l2Msg := msg.L2msg
return parseEspressoTx(bytes.NewReader(l2Msg))
return parseEspressoMsg(bytes.NewReader(l2Msg))
}

func ParseL2Transactions(msg *arbostypes.L1IncomingMessage, chainId *big.Int, batchFetcher InfallibleBatchFetcher) (types.Transactions, error) {
Expand Down Expand Up @@ -162,12 +163,10 @@ func parseL2Message(rd io.Reader, poster common.Address, timestamp uint64, reque
}
nestedSegments, err := parseL2Message(bytes.NewReader(nextMsg), poster, timestamp, nextRequestId, chainId, depth+1)
if err != nil {
log.Warn("Failed to parse L2Message in a batch")
}
if nestedSegments != nil && nestedSegments.Len() > 0 {
segments = append(segments, nestedSegments...)
index.Add(index, big.NewInt(1))
return nil, err
}
segments = append(segments, nestedSegments...)
index.Add(index, big.NewInt(1))
}
case L2MessageKind_SignedTx:
newTx := new(types.Transaction)
Expand All @@ -185,20 +184,26 @@ func parseL2Message(rd io.Reader, poster common.Address, timestamp uint64, reque
}
return types.Transactions{newTx}, nil
case L2MessageKind_EspressoTx:
newTx := new(types.Transaction)
// Safe to read in its entirety, as all input readers are limited
readBytes, err := io.ReadAll(rd)
if err != nil {
return nil, err
}
if err := json.Unmarshal(readBytes, &newTx); err != nil {
return nil, err
}
if newTx.Type() >= types.ArbitrumDepositTxType {
// Should be unreachable due to not accepting Arbitrum internal txs
return nil, types.ErrTxTypeNotSupported
segments := make(types.Transactions, 0)
jst := true
for {
nextMsg, err := util.BytestringFromReader(rd, arbostypes.MaxL2MessageSize)
if err != nil {
// an error here means there are no further messages in the batch
// nolint:nilerr
return segments, nil
}
if jst {
// In espresso transactions batch, the first msg is always the BlockJustification
jst = false
continue
}
newTx := new(types.Transaction)
if err := json.Unmarshal(nextMsg, &newTx); err != nil {
return nil, err
}
segments = append(segments, newTx)
}
return types.Transactions{newTx}, nil
case L2MessageKind_Heartbeat:
if timestamp >= HeartbeatsDisabledAt {
return nil, errors.New("heartbeat messages have been disabled")
Expand All @@ -213,36 +218,35 @@ func parseL2Message(rd io.Reader, poster common.Address, timestamp uint64, reque
}
}

func parseEspressoTx(rd io.Reader) ([]espresso.Bytes, error) {
func parseEspressoMsg(rd io.Reader) ([]espresso.Bytes, *arbostypes.EspressoBlockJustification, error) {
var l2KindBuf [1]byte
if _, err := rd.Read(l2KindBuf[:]); err != nil {
return nil, err
return nil, nil, err
}

switch l2KindBuf[0] {
case L2MessageKind_EspressoTx:
readBytes, err := io.ReadAll(rd)
if err != nil {
return nil, err
}
return []espresso.Bytes{readBytes}, nil
case L2MessageKind_Batch:
txs := make([]espresso.Bytes, 0)
var jst *arbostypes.EspressoBlockJustification
for {
nextMsg, err := util.BytestringFromReader(rd, arbostypes.MaxL2MessageSize)
if err != nil {
// an error here means there are no further messages in the batch
// nolint:nilerr
return txs, nil
return txs, jst, nil
}
next, err := parseEspressoTx(bytes.NewReader(nextMsg))
if err != nil {
return nil, err
if jst == nil {
j := new(arbostypes.EspressoBlockJustification)
if err := json.Unmarshal(nextMsg, &j); err != nil {
return nil, nil, err
}
jst = j
continue
}
txs = append(txs, next...)
txs = append(txs, nextMsg)
}
default:
return nil, errors.New("Unexpected l2 message kind")
return nil, nil, errors.New("Unexpected l2 message kind")
}
}

Expand Down Expand Up @@ -457,3 +461,35 @@ func parseBatchPostingReportMessage(rd io.Reader, chainId *big.Int, msgBatchGasC
// don't need to fill in the other fields, since they exist only to ensure uniqueness, and batchNum is already unique
}), nil
}

// messageFromEspresso serializes raw data from the espresso block into an arbitrum message,
// including malformed and invalid transactions.
// This allows validators to rebuild a block and check the espresso commitment.
//
// Note that the raw data is actually in JSON format, which can result in a larger size than necessary.
// Storing it in L1 call data would lead to some waste. However, for the sake of this Proof of Concept,
// this is deemed acceptable. Addtionally, after we finish the integration, there is no need to store
// message in L1.
func MessageFromEspresso(header *arbostypes.L1IncomingMessageHeader, txes []espresso.Bytes, jst *arbostypes.EspressoBlockJustification) (arbostypes.L1IncomingMessage, error) {
var l2Message []byte

l2Message = append(l2Message, L2MessageKind_EspressoTx)
jstJson, err := json.Marshal(jst)
if err != nil {
return arbostypes.L1IncomingMessage{}, err
}
sizeBuf := make([]byte, 8)
binary.BigEndian.PutUint64(sizeBuf, uint64(len(jstJson)))
l2Message = append(l2Message, sizeBuf...)
l2Message = append(l2Message, jstJson...)
for _, tx := range txes {
binary.BigEndian.PutUint64(sizeBuf, uint64(len(tx)))
l2Message = append(l2Message, sizeBuf...)
l2Message = append(l2Message, tx...)
}

return arbostypes.L1IncomingMessage{
Header: header,
L2msg: l2Message,
}, nil
}
44 changes: 44 additions & 0 deletions arbos/parse_l2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package arbos

import (
"reflect"
"testing"

"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbos/espresso"
)

func TestEspressoParsing(t *testing.T) {
expectTxes := []espresso.Bytes{
[]byte{1, 2, 3},
[]byte{4},
}
expectHeader := &arbostypes.L1IncomingMessageHeader{
Kind: arbostypes.L1MessageType_L2Message,
BlockNumber: 1,
}
expectJst := &arbostypes.EspressoBlockJustification{
Header: espresso.Header{
TransactionsRoot: espresso.NmtRoot{Root: []byte{7, 8, 9}},
Metadata: espresso.Metadata{
L1Head: 1,
Timestamp: 2,
},
},
Proof: []byte{9},
}
msg, err := MessageFromEspresso(expectHeader, expectTxes, expectJst)
Require(t, err)

actualTxes, actualJst, err := ParseEspressoMsg(&msg)
Require(t, err)

if !reflect.DeepEqual(actualTxes, expectTxes) {
Fail(t)
}

if !reflect.DeepEqual(actualJst, expectJst) {
Fail(t)
}

}
12 changes: 5 additions & 7 deletions cmd/replay/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,22 +250,20 @@ func main() {
// TODO test: https://github.com/EspressoSystems/espresso-sequencer/issues/772
if chainConfig.Espresso {
hotShotCommitment := getHotShotCommitment(seqNum)
jst := message.Message.Header.BlockJustification
txs, jst, err := arbos.ParseEspressoMsg(message.Message)
if err != nil {
panic(err)
}
if jst == nil {
panic("batch missing espresso justification")

}
hotshotHeader := jst.Header
if *hotShotCommitment != hotshotHeader.Commit() {
panic("invalid hotshot header")
}
var roots = []*espresso.NmtRoot{&hotshotHeader.TransactionsRoot}
var proofs = []*espresso.NmtProof{&message.Message.Header.BlockJustification.Proof}
var proofs = []*espresso.NmtProof{&jst.Proof}

txs, err := arbos.ParseEspressoTransactions(message.Message)
if err != nil {
panic(err)
}
err = espresso.ValidateBatchTransactions(chainConfig.ChainID.Uint64(), roots, proofs, txs)
if err != nil {
panic(err)
Expand Down
11 changes: 6 additions & 5 deletions execution/gethexec/espresso_sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ func (s *EspressoSequencer) createBlock(ctx context.Context) (returnValue bool)
Timestamp: header.Timestamp,
RequestId: nil,
L1BaseFee: nil,
BlockJustification: &arbostypes.EspressoBlockJustification{
Header: header,
Proof: arbTxns.Proof,
},
}

_, err = s.execEngine.SequenceTransactionsEspresso(arbHeader, arbTxns.Transactions)
jst := &arbostypes.EspressoBlockJustification{
Header: header,
Proof: arbTxns.Proof,
}

_, err = s.execEngine.SequenceTransactionsEspresso(arbHeader, arbTxns.Transactions, jst)
if err != nil {
log.Error("Sequencing error for block number", "block_num", nextSeqBlockNum, "err", err)
return false
Expand Down
45 changes: 9 additions & 36 deletions execution/gethexec/executionengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,40 +154,6 @@ func (s *ExecutionEngine) NextDelayedMessageNumber() (uint64, error) {
return currentHeader.Nonce.Uint64(), nil
}

// messageFromEspresso serializes raw data from the espresso block into an arbitrum message,
// including malformed and invalid transactions.
// This allows validators to rebuild a block and check the espresso commitment.
//
// Note that the raw data is actually in JSON format, which can result in a larger size than necessary.
// Storing it in L1 call data would lead to some waste. However, for the sake of this Proof of Concept,
// this is deemed acceptable. Addtionally, after we finish the integration, there is no need to store
// message in L1.
//
// Refer to `execution/gethexec/executionengine.go messageFromTxes`
func messageFromEspresso(header *arbostypes.L1IncomingMessageHeader, txes []espresso.Bytes) arbostypes.L1IncomingMessage {
var l2Message []byte

if len(txes) == 1 {
l2Message = append(l2Message, arbos.L2MessageKind_EspressoTx)
l2Message = append(l2Message, txes[0]...)
} else {
l2Message = append(l2Message, arbos.L2MessageKind_Batch)
sizeBuf := make([]byte, 8)
for _, tx := range txes {
binary.BigEndian.PutUint64(sizeBuf, uint64(len(tx)+1))
l2Message = append(l2Message, sizeBuf...)
l2Message = append(l2Message, arbos.L2MessageKind_EspressoTx)
l2Message = append(l2Message, tx...)
}

}

return arbostypes.L1IncomingMessage{
Header: header,
L2msg: l2Message,
}
}

func messageFromTxes(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, txErrors []error) (*arbostypes.L1IncomingMessage, error) {
var l2Message []byte
if len(txes) == 1 && txErrors[0] == nil {
Expand Down Expand Up @@ -311,12 +277,19 @@ func (s *ExecutionEngine) SequenceTransactions(header *arbostypes.L1IncomingMess
})
}

func (s *ExecutionEngine) SequenceTransactionsEspresso(header *arbostypes.L1IncomingMessageHeader, rawTxes []espresso.Bytes) (*types.Block, error) {
func (s *ExecutionEngine) SequenceTransactionsEspresso(
header *arbostypes.L1IncomingMessageHeader,
rawTxes []espresso.Bytes,
jst *arbostypes.EspressoBlockJustification,
) (*types.Block, error) {
return s.sequencerWrapper(func() (*types.Block, error) {

// Create the message. This message includes all the raw transactions from
// Espresso Sequencer.
msg := messageFromEspresso(header, rawTxes)
msg, err := arbos.MessageFromEspresso(header, rawTxes, jst)
if err != nil {
return nil, err
}

// Deserialize the transactions and ignore the malformed transactions
txes := types.Transactions{}
Expand Down

0 comments on commit 1868c43

Please sign in to comment.