Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dencun header changes #7993

Merged
merged 23 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
556e500
Pull in basic types from op-service/eth package.
Nov 1, 2023
ba2ec85
Pull in basic types from op-service/sources.
Nov 1, 2023
6b4cb22
Pull in blob and blob_test from PR.
Nov 1, 2023
78716c9
Revert "Pull in basic types from op-service/eth package."
Nov 1, 2023
c174cb5
Add placeholder for dencun fork test.
Nov 1, 2023
149a6bc
Add filler scaffolding for dencun fork test: literally the old shapel…
Nov 1, 2023
4806862
Start to tweak things in dencun_fork_test: mainly replacing "shanghai…
Nov 1, 2023
adcdf91
More minor test tweaks: change require statement's message, get head'…
Nov 1, 2023
118ce7f
Fix usage of IsCancun.
Nov 2, 2023
d0a7922
Tweak test message: really we're just testing isCancun, so acknowledg…
Nov 2, 2023
5c584ca
Use types.Withdrawals in rpcBlock, not eth.Withdrawals.
Nov 2, 2023
d57f75f
Flag the failing line, it's non-obvious in CI output.
Nov 2, 2023
bc2b4de
Add how test is failing just for onlookers.
Nov 2, 2023
79932ba
Add issue links per semgrep request.
Nov 2, 2023
1131944
WIP stash for discussion.
Nov 3, 2023
8389e8c
Commit missed hunk editing a comment in dencun_fork_test.
Nov 3, 2023
acf1c64
Remove useless call to ParentBeaconRoot.
Nov 6, 2023
65916c5
Remove some commented-out cruft.
Nov 6, 2023
2c57e7d
Set necessary header fields if config is cancun.
Nov 6, 2023
78eeb2a
Preserve co-authorship.
Nov 6, 2023
5376fc8
return check for nil tx in rpc block verification
Nov 7, 2023
2b1378f
return withdrawals check in rpc block verification.
Nov 7, 2023
e289de0
remove blob.go and blob_test.go. they'll come back in in another PR w…
Nov 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions op-e2e/actions/dencun_fork_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package actions

import (
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-service/testlog"
)

func TestDencunL1Fork(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
activation := sd.L1Cfg.Timestamp + 24
sd.L1Cfg.Config.CancunTime = &activation
log := testlog.Logger(t, log.LvlDebug)
_, _, miner, sequencer, _, verifier, _, batcher := setupReorgTestActors(t, dp, sd, log)

l1Head := miner.l1Chain.CurrentBlock()
require.False(t, sd.L1Cfg.Config.IsCancun(l1Head.Number, l1Head.Time), "Cancun not active yet")

// start op-nodes
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)

// build empty L1 blocks, crossing the fork boundary
miner.ActL1SetFeeRecipient(common.Address{'A', 0})
miner.ActEmptyBlock(t)
miner.ActEmptyBlock(t) // Cancun activates here
miner.ActEmptyBlock(t)
// verify Cancun is active
l1Head = miner.l1Chain.CurrentBlock()
require.True(t, sd.L1Cfg.Config.IsCancun(l1Head.Number, l1Head.Time), "Cancun active")

// build L2 chain up to and including L2 blocks referencing Cancun L1 blocks
sequencer.ActL1HeadSignal(t)
sequencer.ActBuildToL1Head(t)
miner.ActL1StartBlock(12)(t)
batcher.ActSubmitAll(t)
miner.ActL1IncludeTx(batcher.batcherAddr)(t)
miner.ActL1EndBlock(t)

// sync verifier
verifier.ActL1HeadSignal(t)
verifier.ActL2PipelineFull(t)
// verify verifier accepted Cancun L1 inputs
require.Equal(t, l1Head.Hash(), verifier.SyncStatus().SafeL2.L1Origin.Hash, "verifier synced L1 chain that includes Cancun headers")
require.Equal(t, sequencer.SyncStatus().UnsafeL2, verifier.SyncStatus().UnsafeL2, "verifier and sequencer agree")
}
7 changes: 7 additions & 0 deletions op-e2e/actions/l1_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ func (s *L1Miner) ActL1StartBlock(timeDelta uint64) Action {
if s.l1Cfg.Config.IsShanghai(header.Number, header.Time) {
header.WithdrawalsHash = &types.EmptyWithdrawalsHash
}
if s.l1Cfg.Config.IsCancun(header.Number, header.Time) {
var root common.Hash
var zero uint64
header.BlobGasUsed = &zero
header.ExcessBlobGas = &zero
header.ParentBeaconRoot = &root
}

s.l1Building = true
s.l1BuildingHeader = header
Expand Down
133 changes: 133 additions & 0 deletions op-service/eth/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package eth

import (
"crypto/sha256"
"encoding/binary"
"fmt"
"reflect"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
)

const (
BlobSize = 4096 * 32
MaxBlobDataSize = 4096*31 - 4
)

type Blob [BlobSize]byte

func (b *Blob) KZGBlob() *kzg4844.Blob {
return (*kzg4844.Blob)(b)
}

func (b *Blob) UnmarshalJSON(text []byte) error {
return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:])
}

func (b *Blob) UnmarshalText(text []byte) error {
return hexutil.UnmarshalFixedText("Bytes32", text, b[:])
}

func (b *Blob) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}

func (b *Blob) String() string {
return hexutil.Encode(b[:])
}

// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
func (b *Blob) TerminalString() string {
return fmt.Sprintf("%x..%x", b[:3], b[BlobSize-3:])
}

func (b *Blob) ComputeKZGCommitment() (kzg4844.Commitment, error) {
return kzg4844.BlobToCommitment(*b.KZGBlob())
}

// KZGToVersionedHash computes the "blob hash" (a.k.a. versioned-hash) of a blob-commitment, as used in a blob-tx.
// We implement it here because it is unfortunately not (currently) exposed by geth.
func KZGToVersionedHash(commitment kzg4844.Commitment) (out common.Hash) {
// EIP-4844 spec:
// def kzg_to_versioned_hash(commitment: KZGCommitment) -> VersionedHash:
// return VERSIONED_HASH_VERSION_KZG + sha256(commitment)[1:]
h := sha256.New()
h.Write(commitment[:])
_ = h.Sum(out[:0])
out[0] = params.BlobTxHashVersion
return out
}
ajsutton marked this conversation as resolved.
Show resolved Hide resolved

// VerifyBlobProof verifies that the given blob and proof corresponds to the given commitment,
// returning error if the verification fails.
func VerifyBlobProof(blob *Blob, commitment kzg4844.Commitment, proof kzg4844.Proof) error {
return kzg4844.VerifyBlobProof(*blob.KZGBlob(), commitment, proof)
}

// FromData encodes the given input data into this blob. The encoding scheme is as follows:
//
// First, field elements are encoded as big-endian uint256 in BLS modulus range. To avoid modulus
// overflow, we can't use the full 32 bytes, so we write data only to the topmost 31 bytes of each.
// TODO(client-pod#170): we can optimize this to get a bit more data from the blobs by using the top byte
// partially.
//
// The first field element encodes the length of input data as a little endian uint32 in its
// topmost 4 (out of 31) bytes, and the first 27 bytes of the input data in its remaining 27
// bytes.
//
// The remaining field elements each encode 31 bytes of the remaining input data, up until the end
// of the input.
//
// TODO(client-pod#169): version the encoding format to allow for future encoding changes
func (b *Blob) FromData(data Data) error {
if len(data) > MaxBlobDataSize {
return fmt.Errorf("data is too large for blob. len=%v", len(data))
}
b.Clear()
// encode 4-byte little-endian length value into topmost 4 bytes (out of 31) of first field
// element
binary.LittleEndian.PutUint32(b[1:5], uint32(len(data)))
// encode first 27 bytes of input data into remaining bytes of first field element
offset := copy(b[5:32], data)
// encode (up to) 31 bytes of remaining input data at a time into the subsequent field element
for i := 1; i < 4096; i++ {
offset += copy(b[i*32+1:i*32+32], data[offset:])
if offset == len(data) {
break
}
}
if offset < len(data) {
return fmt.Errorf("failed to fit all data into blob. bytes remaining: %v", len(data)-offset)
}
return nil
}

// ToData decodes the blob into raw byte data. See FromData above for details on the encoding
// format.
func (b *Blob) ToData() (Data, error) {
data := make(Data, 4096*32)
for i := 0; i < 4096; i++ {
if b[i*32] != 0 {
return nil, fmt.Errorf("invalid blob, found non-zero high order byte %x of field element %d", b[i*32], i)
}
copy(data[i*31:i*31+31], b[i*32+1:i*32+32])
}
// extract the length prefix & trim the output accordingly
dataLen := binary.LittleEndian.Uint32(data[:4])
data = data[4:]
if dataLen > uint32(len(data)) {
return nil, fmt.Errorf("invalid blob, length prefix out of range: %d", dataLen)
}
data = data[:dataLen]
return data, nil
}

func (b *Blob) Clear() {
for i := 0; i < BlobSize; i++ {
b[i] = 0
}
}
78 changes: 78 additions & 0 deletions op-service/eth/blob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package eth

import (
"testing"
)

func TestBlobEncodeDecode(t *testing.T) {
cases := []string{
"this is a test of blob encoding/decoding",
"short",
"\x00",
"\x00\x01\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
"",
}

var b Blob
for _, c := range cases {
data := Data(c)
if err := b.FromData(data); err != nil {
t.Fatalf("failed to encode bytes: %v", err)
}
decoded, err := b.ToData()
if err != nil {
t.Fatalf("failed to decode blob: %v", err)
}
if string(decoded) != c {
t.Errorf("decoded != input. got: %v, want: %v", decoded, Data(c))
}
}
}

func TestBigBlobEncoding(t *testing.T) {
bigData := Data(make([]byte, MaxBlobDataSize))
bigData[MaxBlobDataSize-1] = 0xFF
var b Blob
if err := b.FromData(bigData); err != nil {
t.Fatalf("failed to encode bytes: %v", err)
}
decoded, err := b.ToData()
if err != nil {
t.Fatalf("failed to decode blob: %v", err)
}
if string(decoded) != string(bigData) {
t.Errorf("decoded blob != big blob input")
}
}

func TestInvalidBlobDecoding(t *testing.T) {
data := Data("this is a test of invalid blob decoding")
var b Blob
if err := b.FromData(data); err != nil {
t.Fatalf("failed to encode bytes: %v", err)
}
b[32] = 0x80 // field elements should never have their highest order bit set
if _, err := b.ToData(); err == nil {
t.Errorf("expected error, got none")
}

b[32] = 0x00
b[4] = 0xFF // encode an invalid (much too long) length prefix
if _, err := b.ToData(); err == nil {
t.Errorf("expected error, got none")
}
}

func TestTooLongDataEncoding(t *testing.T) {
// should never be able to encode data that has size the same as that of the blob due to < 256
// bit precision of each field element
data := Data(make([]byte, BlobSize))
var b Blob
err := b.FromData(data)
if err == nil {
t.Errorf("expected error, got none")
}
}
17 changes: 15 additions & 2 deletions op-service/sources/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,16 @@ type rpcHeader struct {
BaseFee *hexutil.Big `json:"baseFeePerGas"`

// WithdrawalsRoot was added by EIP-4895 and is ignored in legacy headers.
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot"`
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`

// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed,omitempty"`

// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas,omitempty"`

// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"`

// untrusted info included by RPC, may have to be checked
Hash common.Hash `json:"hash"`
Expand Down Expand Up @@ -156,6 +165,10 @@ func (hdr *rpcHeader) createGethHeader() *types.Header {
Nonce: hdr.Nonce,
BaseFee: (*big.Int)(hdr.BaseFee),
WithdrawalsHash: hdr.WithdrawalsRoot,
// Cancun
BlobGasUsed: (*uint64)(hdr.BlobGasUsed),
ExcessBlobGas: (*uint64)(hdr.ExcessBlobGas),
ParentBeaconRoot: hdr.ParentBeaconRoot,
}
}

Expand Down Expand Up @@ -185,7 +198,7 @@ func (block *rpcBlock) verify() error {
}
for i, tx := range block.Transactions {
if tx == nil {
return fmt.Errorf("block tx %d is null", i)
return fmt.Errorf("block tx %d is nil", i)
}
}
if computed := types.DeriveSha(types.Transactions(block.Transactions), trie.NewStackTrie(nil)); block.TxHash != computed {
Expand Down