Commit 5c51b177 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #5460 from ethereum-optimism/jg/reader_api

op-node: Reader/Writer API for marshaling code
parents 2fa4d36b 8e793bf9
...@@ -2,9 +2,8 @@ package derive ...@@ -2,9 +2,8 @@ package derive
import ( import (
"bytes" "bytes"
"encoding/binary" "errors"
"fmt" "fmt"
"io"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -13,6 +12,7 @@ import ( ...@@ -13,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/predeploys" "github.com/ethereum-optimism/optimism/op-bindings/predeploys"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-service/solabi"
) )
const ( const (
...@@ -46,52 +46,51 @@ type L1BlockInfo struct { ...@@ -46,52 +46,51 @@ type L1BlockInfo struct {
L1FeeScalar eth.Bytes32 L1FeeScalar eth.Bytes32
} }
//+---------+--------------------------+ // Binary Format
//| Bytes | Field | // +---------+--------------------------+
//+---------+--------------------------+ // | Bytes | Field |
//| 4 | Function signature | // +---------+--------------------------+
//| 24 | Padding for Number | // | 4 | Function signature |
//| 8 | Number | // | 32 | Number |
//| 24 | Padding for Time | // | 32 | Time |
//| 8 | Time | // | 32 | BaseFee |
//| 32 | BaseFee | // | 32 | BlockHash |
//| 32 | BlockHash | // | 32 | SequenceNumber |
//| 24 | Padding for SequenceNumber| // | 32 | BatcherAddr |
//| 8 | SequenceNumber | // | 32 | L1FeeOverhead |
//| 12 | Padding for BatcherAddr | // | 32 | L1FeeScalar |
//| 20 | BatcherAddr | // +---------+--------------------------+
//| 32 | L1FeeOverhead |
//| 32 | L1FeeScalar |
//+---------+--------------------------+
func (info *L1BlockInfo) MarshalBinary() ([]byte, error) { func (info *L1BlockInfo) MarshalBinary() ([]byte, error) {
writer := bytes.NewBuffer(make([]byte, 0, L1InfoLen)) w := bytes.NewBuffer(make([]byte, 0, L1InfoLen))
if err := solabi.WriteSignature(w, L1InfoFuncBytes4); err != nil {
writer.Write(L1InfoFuncBytes4)
if err := writeSolidityABIUint64(writer, info.Number); err != nil {
return nil, err return nil, err
} }
if err := writeSolidityABIUint64(writer, info.Time); err != nil { if err := solabi.WriteUint64(w, info.Number); err != nil {
return nil, err return nil, err
} }
// Ensure that the baseFee is not too large. if err := solabi.WriteUint64(w, info.Time); err != nil {
if info.BaseFee.BitLen() > 256 { return nil, err
return nil, fmt.Errorf("base fee exceeds 256 bits: %d", info.BaseFee)
} }
var baseFeeBuf [32]byte if err := solabi.WriteUint256(w, info.BaseFee); err != nil {
info.BaseFee.FillBytes(baseFeeBuf[:])
writer.Write(baseFeeBuf[:])
writer.Write(info.BlockHash.Bytes())
if err := writeSolidityABIUint64(writer, info.SequenceNumber); err != nil {
return nil, err return nil, err
} }
if err := solabi.WriteHash(w, info.BlockHash); err != nil {
var addrPadding [12]byte return nil, err
writer.Write(addrPadding[:]) }
writer.Write(info.BatcherAddr.Bytes()) if err := solabi.WriteUint64(w, info.SequenceNumber); err != nil {
writer.Write(info.L1FeeOverhead[:]) return nil, err
writer.Write(info.L1FeeScalar[:]) }
return writer.Bytes(), nil if err := solabi.WriteAddress(w, info.BatcherAddr); err != nil {
return nil, err
}
if err := solabi.WriteEthBytes32(w, info.L1FeeOverhead); err != nil {
return nil, err
}
if err := solabi.WriteEthBytes32(w, info.L1FeeScalar); err != nil {
return nil, err
}
return w.Bytes(), nil
} }
func (info *L1BlockInfo) UnmarshalBinary(data []byte) error { func (info *L1BlockInfo) UnmarshalBinary(data []byte) error {
...@@ -100,81 +99,40 @@ func (info *L1BlockInfo) UnmarshalBinary(data []byte) error { ...@@ -100,81 +99,40 @@ func (info *L1BlockInfo) UnmarshalBinary(data []byte) error {
} }
reader := bytes.NewReader(data) reader := bytes.NewReader(data)
funcSignature := make([]byte, 4) var err error
if _, err := io.ReadFull(reader, funcSignature); err != nil || !bytes.Equal(funcSignature, L1InfoFuncBytes4) { if _, err := solabi.ReadAndValidateSignature(reader, L1InfoFuncBytes4); err != nil {
return fmt.Errorf("data does not match L1 info function signature: 0x%x", funcSignature)
}
if blockNumber, err := readSolidityABIUint64(reader); err != nil {
return err return err
} else {
info.Number = blockNumber
} }
if blockTime, err := readSolidityABIUint64(reader); err != nil { if info.Number, err = solabi.ReadUint64(reader); err != nil {
return err return err
} else {
info.Time = blockTime
} }
if info.Time, err = solabi.ReadUint64(reader); err != nil {
var baseFeeBytes [32]byte
if _, err := io.ReadFull(reader, baseFeeBytes[:]); err != nil {
return fmt.Errorf("expected BaseFee length to be 32 bytes, but got %x", baseFeeBytes)
}
info.BaseFee = new(big.Int).SetBytes(baseFeeBytes[:])
var blockHashBytes [32]byte
if _, err := io.ReadFull(reader, blockHashBytes[:]); err != nil {
return fmt.Errorf("expected BlockHash length to be 32 bytes, but got %x", blockHashBytes)
}
info.BlockHash.SetBytes(blockHashBytes[:])
if sequenceNumber, err := readSolidityABIUint64(reader); err != nil {
return err return err
} else {
info.SequenceNumber = sequenceNumber
} }
if info.BaseFee, err = solabi.ReadUint256(reader); err != nil {
var addrPadding [12]byte return err
if _, err := io.ReadFull(reader, addrPadding[:]); err != nil {
return fmt.Errorf("expected addrPadding length to be 12 bytes, but got %x", addrPadding)
} }
if _, err := io.ReadFull(reader, info.BatcherAddr[:]); err != nil { if info.BlockHash, err = solabi.ReadHash(reader); err != nil {
return fmt.Errorf("expected BatcherAddr length to be 20 bytes, but got %x", info.BatcherAddr) return err
} }
if _, err := io.ReadFull(reader, info.L1FeeOverhead[:]); err != nil { if info.SequenceNumber, err = solabi.ReadUint64(reader); err != nil {
return fmt.Errorf("expected L1FeeOverhead length to be 32 bytes, but got %x", info.L1FeeOverhead) return err
} }
if _, err := io.ReadFull(reader, info.L1FeeScalar[:]); err != nil { if info.BatcherAddr, err = solabi.ReadAddress(reader); err != nil {
return fmt.Errorf("expected L1FeeScalar length to be 32 bytes, but got %x", info.L1FeeScalar) return err
} }
if info.L1FeeOverhead, err = solabi.ReadEthBytes32(reader); err != nil {
return nil
}
func writeSolidityABIUint64(w io.Writer, num uint64) error {
var padding [24]byte
if _, err := w.Write(padding[:]); err != nil {
return err return err
} }
if err := binary.Write(w, binary.BigEndian, num); err != nil { if info.L1FeeScalar, err = solabi.ReadEthBytes32(reader); err != nil {
return err return err
} }
if !solabi.EmptyReader(reader) {
return errors.New("too many bytes")
}
return nil return nil
} }
func readSolidityABIUint64(r io.Reader) (uint64, error) {
var (
padding, readPadding [24]byte
num uint64
)
if _, err := io.ReadFull(r, readPadding[:]); err != nil || !bytes.Equal(readPadding[:], padding[:]) {
return 0, fmt.Errorf("L1BlockInfo number exceeds uint64 bounds: %x", readPadding[:])
}
if err := binary.Read(r, binary.BigEndian, &num); err != nil {
return 0, fmt.Errorf("L1BlockInfo expected number length to be 8 bytes")
}
return num, nil
}
// L1InfoDepositTxData is the inverse of L1InfoDeposit, to see where the L2 chain is derived from // L1InfoDepositTxData is the inverse of L1InfoDeposit, to see where the L2 chain is derived from
func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) { func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) {
var info L1BlockInfo var info L1BlockInfo
......
...@@ -2,7 +2,7 @@ package derive ...@@ -2,7 +2,7 @@ package derive
import ( import (
"bytes" "bytes"
"encoding/binary" "errors"
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/solabi"
) )
var ( var (
...@@ -27,17 +28,6 @@ var ( ...@@ -27,17 +28,6 @@ var (
ConfigUpdateEventVersion0 = common.Hash{} ConfigUpdateEventVersion0 = common.Hash{}
) )
var (
// A left-padded uint256 equal to 32.
oneWordUint = common.Hash{31: 32}
// A left-padded uint256 equal to 64.
twoWordUint = common.Hash{31: 64}
// 24 zero bytes (the padding for a uint64 in a 32 byte word)
uint64Padding = make([]byte, 24)
// 12 zero bytes (the padding for an Ethereum address in a 32 byte word)
addressPadding = make([]byte, 12)
)
// UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg // UpdateSystemConfigWithL1Receipts filters all L1 receipts to find config updates and applies the config updates to the given sysCfg
func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config) error { func UpdateSystemConfigWithL1Receipts(sysCfg *eth.SystemConfig, receipts []*types.Receipt, cfg *rollup.Config) error {
var result error var result error
...@@ -84,90 +74,60 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L ...@@ -84,90 +74,60 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L
// Create a reader of the unindexed data // Create a reader of the unindexed data
reader := bytes.NewReader(ev.Data) reader := bytes.NewReader(ev.Data)
// Counter for the number of bytes read from `reader` via `readWord`
countReadBytes := 0
// Helper function to read a word from the log data reader
readWord := func() (b [32]byte) {
if _, err := reader.Read(b[:]); err != nil {
// If there is an error reading the next 32 bytes from the reader, return an empty
// 32 byte array. We always check that the number of bytes read (`countReadBytes`)
// is equal to the expected amount at the end of each switch case.
return b
}
countReadBytes += 32
return b
}
// Attempt to read unindexed data // Attempt to read unindexed data
switch updateType { switch updateType {
case SystemConfigUpdateBatcher: case SystemConfigUpdateBatcher:
// Read the pointer, it should always equal 32. if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
if word := readWord(); word != oneWordUint { return NewCriticalError(errors.New("invalid pointer field"))
return fmt.Errorf("expected offset to point to length location, but got %s", word)
} }
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
// Read the length, it should also always equal 32. return NewCriticalError(errors.New("invalid length field"))
if word := readWord(); word != oneWordUint {
return fmt.Errorf("expected length to be 32 bytes, but got %s", word)
} }
address, err := solabi.ReadAddress(reader)
// Indexing `word` directly is always safe here, it is guaranteed to be 32 bytes in length. if err != nil {
// Check that the batcher address is correctly zero-padded. return NewCriticalError(errors.New("could not read address"))
word := readWord()
if !bytes.Equal(word[:12], addressPadding) {
return fmt.Errorf("expected version 0 batcher hash with zero padding, but got %x", word)
} }
destSysCfg.BatcherAddr.SetBytes(word[12:]) if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
if countReadBytes != 32*3 {
return NewCriticalError(fmt.Errorf("expected 32*3 bytes in batcher hash update, but got %d bytes", len(ev.Data)))
} }
destSysCfg.BatcherAddr = address
return nil return nil
case SystemConfigUpdateGasConfig: case SystemConfigUpdateGasConfig:
// Read the pointer, it should always equal 32. if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
if word := readWord(); word != oneWordUint { return NewCriticalError(errors.New("invalid pointer field"))
return fmt.Errorf("expected offset to point to length location, but got %s", word)
} }
if length, err := solabi.ReadUint64(reader); err != nil || length != 64 {
// Read the length, it should always equal 64. return NewCriticalError(errors.New("invalid length field"))
if word := readWord(); word != twoWordUint {
return fmt.Errorf("expected length to be 64 bytes, but got %s", word)
} }
overhead, err := solabi.ReadEthBytes32(reader)
// Set the system config's overhead and scalar values to the values read from the log if err != nil {
destSysCfg.Overhead = readWord() return NewCriticalError(errors.New("could not read overhead"))
destSysCfg.Scalar = readWord()
if countReadBytes != 32*4 {
return NewCriticalError(fmt.Errorf("expected 32*4 bytes in GPO params update data, but got %d", len(ev.Data)))
} }
scalar, err := solabi.ReadEthBytes32(reader)
if err != nil {
return NewCriticalError(errors.New("could not read scalar"))
}
if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
}
destSysCfg.Overhead = overhead
destSysCfg.Scalar = scalar
return nil return nil
case SystemConfigUpdateGasLimit: case SystemConfigUpdateGasLimit:
// Read the pointer, it should always equal 32. if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 {
if word := readWord(); word != oneWordUint { return NewCriticalError(errors.New("invalid pointer field"))
return fmt.Errorf("expected offset to point to length location, but got %s", word)
} }
if length, err := solabi.ReadUint64(reader); err != nil || length != 32 {
// Read the length, it should also always equal 32. return NewCriticalError(errors.New("invalid length field"))
if word := readWord(); word != oneWordUint {
return fmt.Errorf("expected length to be 32 bytes, but got %s", word)
} }
gasLimit, err := solabi.ReadUint64(reader)
// Indexing `word` directly is always safe here, it is guaranteed to be 32 bytes in length. if err != nil {
// Check that the gas limit is correctly zero-padded. return NewCriticalError(errors.New("could not read gas limit"))
word := readWord()
if !bytes.Equal(word[:24], uint64Padding) {
return fmt.Errorf("expected zero padding for gaslimit, but got %x", word)
} }
destSysCfg.GasLimit = binary.BigEndian.Uint64(word[24:]) if !solabi.EmptyReader(reader) {
return NewCriticalError(errors.New("too many bytes"))
if countReadBytes != 32*3 {
return NewCriticalError(fmt.Errorf("expected 32*3 bytes in gas limit update, but got %d bytes", len(ev.Data)))
} }
destSysCfg.GasLimit = gasLimit
return nil return nil
case SystemConfigUpdateUnsafeBlockSigner: case SystemConfigUpdateUnsafeBlockSigner:
// Ignored in derivation. This configurable applies to runtime configuration outside of the derivation. // Ignored in derivation. This configurable applies to runtime configuration outside of the derivation.
......
package solabi
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum/go-ethereum/common"
)
// These are empty padding values. They should be zero'd & not modified at all.
var (
addressEmptyPadding [12]byte = [12]byte{}
uint64EmptyPadding [24]byte = [24]byte{}
)
func ReadSignature(r io.Reader) ([]byte, error) {
sig := make([]byte, 4)
_, err := io.ReadFull(r, sig)
return sig, err
}
func ReadAndValidateSignature(r io.Reader, expectedSignature []byte) ([]byte, error) {
sig := make([]byte, 4)
if _, err := io.ReadFull(r, sig); err != nil {
return nil, err
}
if !bytes.Equal(sig, expectedSignature) {
return nil, errors.New("invalid function signature")
}
return sig, nil
}
func ReadHash(r io.Reader) (common.Hash, error) {
var h common.Hash
_, err := io.ReadFull(r, h[:])
return h, err
}
func ReadEthBytes32(r io.Reader) (eth.Bytes32, error) {
var b eth.Bytes32
_, err := io.ReadFull(r, b[:])
return b, err
}
func ReadAddress(r io.Reader) (common.Address, error) {
var readPadding [12]byte
var a common.Address
if _, err := io.ReadFull(r, readPadding[:]); err != nil {
return a, err
} else if !bytes.Equal(readPadding[:], addressEmptyPadding[:]) {
return a, fmt.Errorf("address padding was not empty: %x", readPadding[:])
}
_, err := io.ReadFull(r, a[:])
return a, err
}
// ReadUint64 reads a big endian uint64 from a 32 byte word
func ReadUint64(r io.Reader) (uint64, error) {
var readPadding [24]byte
var n uint64
if _, err := io.ReadFull(r, readPadding[:]); err != nil {
return n, err
} else if !bytes.Equal(readPadding[:], uint64EmptyPadding[:]) {
return n, fmt.Errorf("number padding was not empty: %x", readPadding[:])
}
if err := binary.Read(r, binary.BigEndian, &n); err != nil {
return 0, fmt.Errorf("expected number length to be 8 bytes")
}
return n, nil
}
func ReadUint256(r io.Reader) (*big.Int, error) {
var n [32]byte
if _, err := io.ReadFull(r, n[:]); err != nil {
return nil, err
}
return new(big.Int).SetBytes(n[:]), nil
}
func EmptyReader(r io.Reader) bool {
var t [1]byte
n, err := r.Read(t[:])
return n == 0 && err == io.EOF
}
func WriteSignature(w io.Writer, sig []byte) error {
_, err := w.Write(sig)
return err
}
func WriteHash(w io.Writer, h common.Hash) error {
_, err := w.Write(h[:])
return err
}
func WriteEthBytes32(w io.Writer, b eth.Bytes32) error {
_, err := w.Write(b[:])
return err
}
func WriteAddress(w io.Writer, a common.Address) error {
if _, err := w.Write(addressEmptyPadding[:]); err != nil {
return err
}
if _, err := w.Write(a[:]); err != nil {
return err
}
return nil
}
func WriteUint256(w io.Writer, n *big.Int) error {
if n.BitLen() > 256 {
return fmt.Errorf("big int exceeds 256 bits: %d", n)
}
arr := make([]byte, 32)
n.FillBytes(arr)
_, err := w.Write(arr)
return err
}
func WriteUint64(w io.Writer, n uint64) error {
if _, err := w.Write(uint64EmptyPadding[:]); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, n); err != nil {
return err
}
return nil
}
package solabi_test
import (
"bytes"
"testing"
"github.com/ethereum-optimism/optimism/op-service/solabi"
"github.com/stretchr/testify/require"
)
func TestEmptyReader(t *testing.T) {
t.Run("empty", func(t *testing.T) {
r := new(bytes.Buffer)
require.True(t, solabi.EmptyReader(r))
})
t.Run("empty after read", func(t *testing.T) {
r := bytes.NewBufferString("not empty")
tmp := make([]byte, 9)
n, err := r.Read(tmp)
require.Equal(t, 9, n)
require.NoError(t, err)
require.True(t, solabi.EmptyReader(r))
})
t.Run("extra bytes", func(t *testing.T) {
r := bytes.NewBufferString("not empty")
require.False(t, solabi.EmptyReader(r))
})
}
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