Commit 3124c8f0 authored by protolambda's avatar protolambda

op-chain-ops, op-e2e: L1 Dencun config and setup support

Co-authored-by: default avatarRoberto Bayardo <bayardo@alum.mit.edu>
parent 4d6bb0db
...@@ -70,6 +70,8 @@ func NewBackendWithGenesisTimestamp(ts uint64, shanghai bool) *backends.Simulate ...@@ -70,6 +70,8 @@ func NewBackendWithGenesisTimestamp(ts uint64, shanghai bool) *backends.Simulate
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0),
ShanghaiTime: nil,
CancunTime: nil,
// Activated proof of stake. We manually build/commit blocks in the simulator anyway, // Activated proof of stake. We manually build/commit blocks in the simulator anyway,
// and the timestamp verification of PoS is not against the wallclock, // and the timestamp verification of PoS is not against the wallclock,
// preventing blocks from getting stuck temporarily in the future-blocks queue, decreasing setup time a lot. // preventing blocks from getting stuck temporarily in the future-blocks queue, decreasing setup time a lot.
......
...@@ -217,6 +217,9 @@ type DeployConfig struct { ...@@ -217,6 +217,9 @@ type DeployConfig struct {
// RequiredProtocolVersion indicates the protocol version that // RequiredProtocolVersion indicates the protocol version that
// nodes are recommended to adopt, to stay in sync with the network. // nodes are recommended to adopt, to stay in sync with the network.
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"` RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
// When Cancun activates. Relative to L1 genesis.
L1CancunTimeOffset *uint64 `json:"l1CancunTimeOffset,omitempty"`
} }
// Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy // Copy will deeply copy the DeployConfig. This does a JSON roundtrip to copy
......
...@@ -64,6 +64,7 @@ func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, erro ...@@ -64,6 +64,7 @@ func NewL2Genesis(config *DeployConfig, block *types.Block) (*core.Genesis, erro
RegolithTime: config.RegolithTime(block.Time()), RegolithTime: config.RegolithTime(block.Time()),
CanyonTime: config.CanyonTime(block.Time()), CanyonTime: config.CanyonTime(block.Time()),
ShanghaiTime: config.CanyonTime(block.Time()), ShanghaiTime: config.CanyonTime(block.Time()),
CancunTime: nil, // no Dencun on L2 yet.
Optimism: &params.OptimismConfig{ Optimism: &params.OptimismConfig{
EIP1559Denominator: eip1559Denom, EIP1559Denominator: eip1559Denom,
EIP1559Elasticity: eip1559Elasticity, EIP1559Elasticity: eip1559Elasticity,
...@@ -134,6 +135,8 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -134,6 +135,8 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
LondonBlock: big.NewInt(0), LondonBlock: big.NewInt(0),
ArrowGlacierBlock: big.NewInt(0), ArrowGlacierBlock: big.NewInt(0),
GrayGlacierBlock: big.NewInt(0), GrayGlacierBlock: big.NewInt(0),
ShanghaiTime: nil,
CancunTime: nil,
} }
extraData := make([]byte, 0) extraData := make([]byte, 0)
...@@ -168,6 +171,10 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { ...@@ -168,6 +171,10 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) {
if timestamp == 0 { if timestamp == 0 {
timestamp = hexutil.Uint64(time.Now().Unix()) timestamp = hexutil.Uint64(time.Now().Unix())
} }
if !config.L1UseClique && config.L1CancunTimeOffset != nil {
cancunTime := uint64(timestamp) + *config.L1CancunTimeOffset
chainConfig.CancunTime = &cancunTime
}
return &core.Genesis{ return &core.Genesis{
Config: &chainConfig, Config: &chainConfig,
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
...@@ -51,6 +52,11 @@ func NewL1Replica(t Testing, log log.Logger, genesis *core.Genesis) *L1Replica { ...@@ -51,6 +52,11 @@ func NewL1Replica(t Testing, log log.Logger, genesis *core.Genesis) *L1Replica {
NetworkId: genesis.Config.ChainID.Uint64(), NetworkId: genesis.Config.ChainID.Uint64(),
Genesis: genesis, Genesis: genesis,
RollupDisableTxPoolGossip: true, RollupDisableTxPoolGossip: true,
BlobPool: blobpool.Config{
Datadir: t.TempDir(),
Datacap: blobpool.DefaultConfig.Datacap,
PriceBump: blobpool.DefaultConfig.PriceBump,
},
} }
nodeCfg := &node.Config{ nodeCfg := &node.Config{
Name: "l1-geth", Name: "l1-geth",
......
package fakebeacon
import (
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/log"
)
// FakeBeacon presents a beacon-node in testing, without leading any chain-building.
// This merely serves a fake beacon API, and holds on to blocks,
// to complement the actual block-building to happen in testing (e.g. through the fake consensus geth module).
type FakeBeacon struct {
log log.Logger
// directory to store blob contents in after the blobs are persisted in a block
blobsDir string
blobsLock sync.Mutex
beaconSrv *http.Server
beaconAPIListener net.Listener
genesisTime uint64
blockTime uint64
}
func NewBeacon(log log.Logger, blobsDir string, genesisTime uint64, blockTime uint64) *FakeBeacon {
return &FakeBeacon{
log: log,
blobsDir: blobsDir,
genesisTime: genesisTime,
blockTime: blockTime,
}
}
func (f *FakeBeacon) Start(addr string) error {
listener, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf("failed to open tcp listener for http beacon api server: %w", err)
}
f.beaconAPIListener = listener
mux := new(http.ServeMux)
mux.HandleFunc("/eth/v1/beacon/genesis", func(w http.ResponseWriter, r *http.Request) {
err := json.NewEncoder(w).Encode(&eth.APIGenesisResponse{Data: eth.ReducedGenesisData{GenesisTime: eth.Uint64String(f.genesisTime)}})
if err != nil {
f.log.Error("genesis handler err", "err", err)
}
})
mux.HandleFunc("/eth/v1/config/spec", func(w http.ResponseWriter, r *http.Request) {
err := json.NewEncoder(w).Encode(&eth.APIConfigResponse{Data: eth.ReducedConfigData{SecondsPerSlot: eth.Uint64String(f.blockTime)}})
if err != nil {
f.log.Error("config handler err", "err", err)
}
})
mux.HandleFunc("/eth/v1/beacon/blob_sidecars/", func(w http.ResponseWriter, r *http.Request) {
blockID := strings.TrimPrefix(r.URL.Path, "/eth/v1/beacon/blob_sidecars/")
slot, err := strconv.ParseUint(blockID, 10, 64)
if err != nil {
f.log.Error("could not parse block id from request", "url", r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
return
}
bundle, err := f.LoadBlobsBundle(slot)
if err != nil {
f.log.Error("failed to load blobs bundle", "slot", slot)
w.WriteHeader(http.StatusInternalServerError)
return
}
query := r.URL.Query()
rawIndices := query["indices"]
indices := make([]int, 0, len(bundle.Blobs))
if len(rawIndices) == 0 {
// request is for all blobs
for i := range bundle.Blobs {
indices = append(indices, i)
}
} else {
for _, raw := range rawIndices {
ix, err := strconv.ParseUint(raw, 10, 64)
if err != nil {
f.log.Error("could not parse index from request", "url", r.URL)
w.WriteHeader(http.StatusBadRequest)
return
}
indices = append(indices, int(ix))
}
}
var mockBeaconBlockRoot [32]byte
mockBeaconBlockRoot[0] = 42
binary.LittleEndian.PutUint64(mockBeaconBlockRoot[32-8:], slot)
sidecars := make([]*eth.BlobSidecar, len(indices))
for i, ix := range indices {
if ix < 0 || ix >= len(bundle.Blobs) {
f.log.Error("blob index from request is out of range", "url", r.URL)
w.WriteHeader(http.StatusBadRequest)
return
}
sidecars[i] = &eth.BlobSidecar{
BlockRoot: mockBeaconBlockRoot,
Slot: eth.Uint64String(slot),
Index: eth.Uint64String(i),
KZGCommitment: eth.Bytes48(bundle.Commitments[ix]),
KZGProof: eth.Bytes48(bundle.Proofs[ix]),
}
copy(sidecars[i].Blob[:], bundle.Blobs[ix])
}
if err := json.NewEncoder(w).Encode(&eth.APIGetBlobSidecarsResponse{Data: sidecars}); err != nil {
f.log.Error("blobs handler err", "err", err)
}
})
f.beaconSrv = &http.Server{
Handler: mux,
ReadTimeout: time.Second * 20,
ReadHeaderTimeout: time.Second * 20,
WriteTimeout: time.Second * 20,
IdleTimeout: time.Second * 20,
}
go func() {
if err := f.beaconSrv.Serve(f.beaconAPIListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
f.log.Error("failed to start fake-pos beacon server for blobs testing", "err", err)
}
}()
return nil
}
func (f *FakeBeacon) StoreBlobsBundle(slot uint64, bundle *engine.BlobsBundleV1) error {
data, err := json.Marshal(bundle)
if err != nil {
return fmt.Errorf("failed to encode blobs bundle of slot %d: %w", slot, err)
}
f.blobsLock.Lock()
defer f.blobsLock.Unlock()
bundlePath := fmt.Sprintf("blobs_bundle_%d.json", slot)
if err := os.MkdirAll(f.blobsDir, 0755); err != nil {
return fmt.Errorf("failed to create dir for blob storage: %w", err)
}
err = os.WriteFile(filepath.Join(f.blobsDir, bundlePath), data, 0755)
if err != nil {
return fmt.Errorf("failed to write blobs bundle of slot %d: %w", slot, err)
}
return nil
}
func (f *FakeBeacon) LoadBlobsBundle(slot uint64) (*engine.BlobsBundleV1, error) {
f.blobsLock.Lock()
defer f.blobsLock.Unlock()
bundlePath := fmt.Sprintf("blobs_bundle_%d.json", slot)
data, err := os.ReadFile(filepath.Join(f.blobsDir, bundlePath))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, fmt.Errorf("no blobs bundle found for slot %d (%q): %w", slot, bundlePath, ethereum.NotFound)
} else {
return nil, fmt.Errorf("failed to read blobs bundle of slot %d (%q): %w", slot, bundlePath, err)
}
}
var out engine.BlobsBundleV1
if err := json.Unmarshal(data, &out); err != nil {
return nil, fmt.Errorf("failed to decode blobs bundle of slot %d (%q): %w", slot, bundlePath, err)
}
return &out, nil
}
func (f *FakeBeacon) Close() error {
var out error
if f.beaconSrv != nil {
out = errors.Join(out, f.beaconSrv.Close())
}
if f.beaconAPIListener != nil {
out = errors.Join(out, f.beaconAPIListener.Close())
}
return out
}
func (f *FakeBeacon) BeaconAddr() string {
return "http://" + f.beaconAPIListener.Addr().String()
}
package geth package geth
import ( import (
"encoding/binary"
"math/big"
"math/rand" "math/rand"
"time" "time"
...@@ -8,15 +10,21 @@ import ( ...@@ -8,15 +10,21 @@ import (
"github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/clock"
opeth "github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils"
) )
type Beacon interface {
StoreBlobsBundle(slot uint64, bundle *engine.BlobsBundleV1) error
}
// fakePoS is a testing-only utility to attach to Geth, // fakePoS is a testing-only utility to attach to Geth,
// to build a fake proof-of-stake L1 chain with fixed block time and basic lagging safe/finalized blocks. // to build a fake proof-of-stake L1 chain with fixed block time and basic lagging safe/finalized blocks.
type fakePoS struct { type fakePoS struct {
...@@ -32,6 +40,14 @@ type fakePoS struct { ...@@ -32,6 +40,14 @@ type fakePoS struct {
engineAPI *catalyst.ConsensusAPI engineAPI *catalyst.ConsensusAPI
sub ethereum.Subscription sub ethereum.Subscription
beacon Beacon
}
func (f *fakePoS) FakeBeaconBlockRoot(time uint64) common.Hash {
var dat [8]byte
binary.LittleEndian.PutUint64(dat[:], time)
return crypto.Keccak256Hash(dat[:])
} }
func (f *fakePoS) Start() error { func (f *fakePoS) Start() error {
...@@ -81,16 +97,29 @@ func (f *fakePoS) Start() error { ...@@ -81,16 +97,29 @@ func (f *fakePoS) Start() error {
Amount: uint64(withdrawalsRNG.Intn(50_000_000_000) + 1), Amount: uint64(withdrawalsRNG.Intn(50_000_000_000) + 1),
} }
} }
res, err := f.engineAPI.ForkchoiceUpdatedV2(engine.ForkchoiceStateV1{ attrs := &engine.PayloadAttributes{
HeadBlockHash: head.Hash(),
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}, &engine.PayloadAttributes{
Timestamp: newBlockTime, Timestamp: newBlockTime,
Random: common.Hash{}, Random: common.Hash{},
SuggestedFeeRecipient: head.Coinbase, SuggestedFeeRecipient: head.Coinbase,
Withdrawals: withdrawals, Withdrawals: withdrawals,
}) }
parentBeaconBlockRoot := f.FakeBeaconBlockRoot(head.Time) // parent beacon block root
isCancun := f.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(head.Number.Uint64()+1), newBlockTime)
if isCancun {
attrs.BeaconRoot = &parentBeaconBlockRoot
}
fcState := engine.ForkchoiceStateV1{
HeadBlockHash: head.Hash(),
SafeBlockHash: safe.Hash(),
FinalizedBlockHash: finalized.Hash(),
}
var err error
var res engine.ForkChoiceResponse
if isCancun {
res, err = f.engineAPI.ForkchoiceUpdatedV3(fcState, attrs)
} else {
res, err = f.engineAPI.ForkchoiceUpdatedV2(fcState, attrs)
}
if err != nil { if err != nil {
f.log.Error("failed to start building L1 block", "err", err) f.log.Error("failed to start building L1 block", "err", err)
continue continue
...@@ -114,10 +143,41 @@ func (f *fakePoS) Start() error { ...@@ -114,10 +143,41 @@ func (f *fakePoS) Start() error {
f.log.Error("failed to finish building L1 block", "err", err) f.log.Error("failed to finish building L1 block", "err", err)
continue continue
} }
if _, err := f.engineAPI.NewPayloadV2(*envelope.ExecutionPayload); err != nil {
f.log.Error("failed to insert built L1 block", "err", err) blobHashes := make([]common.Hash, 0) // must be non-nil even when empty, due to geth engine API checks
for _, commitment := range envelope.BlobsBundle.Commitments {
if len(commitment) != 48 {
f.log.Error("got malformed kzg commitment from engine", "commitment", commitment)
break
}
blobHashes = append(blobHashes, opeth.KZGToVersionedHash(*(*[48]byte)(commitment)))
}
if len(blobHashes) != len(envelope.BlobsBundle.Commitments) {
f.log.Error("invalid or incomplete blob data", "collected", len(blobHashes), "engine", len(envelope.BlobsBundle.Commitments))
continue continue
} }
if isCancun {
if _, err := f.engineAPI.NewPayloadV3(*envelope.ExecutionPayload, blobHashes, &parentBeaconBlockRoot); err != nil {
f.log.Error("failed to insert built L1 block", "err", err)
continue
}
} else {
if _, err := f.engineAPI.NewPayloadV2(*envelope.ExecutionPayload); err != nil {
f.log.Error("failed to insert built L1 block", "err", err)
continue
}
}
if envelope.BlobsBundle != nil {
slot := (envelope.ExecutionPayload.Timestamp - f.eth.BlockChain().Genesis().Time()) / f.blockTime
if f.beacon == nil {
f.log.Error("no blobs storage available")
continue
}
if err := f.beacon.StoreBlobsBundle(slot, envelope.BlobsBundle); err != nil {
f.log.Error("failed to persist blobs-bundle of block, not making block canonical now", "err", err)
continue
}
}
if _, err := f.engineAPI.ForkchoiceUpdatedV2(engine.ForkchoiceStateV1{ if _, err := f.engineAPI.ForkchoiceUpdatedV2(engine.ForkchoiceStateV1{
HeadBlockHash: envelope.ExecutionPayload.BlockHash, HeadBlockHash: envelope.ExecutionPayload.BlockHash,
SafeBlockHash: safe.Hash(), SafeBlockHash: safe.Hash(),
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
...@@ -21,10 +22,15 @@ import ( ...@@ -21,10 +22,15 @@ import (
_ "github.com/ethereum/go-ethereum/eth/tracers/native" _ "github.com/ethereum/go-ethereum/eth/tracers/native"
) )
func InitL1(chainID uint64, blockTime uint64, genesis *core.Genesis, c clock.Clock, opts ...GethOption) (*node.Node, *eth.Ethereum, error) { func InitL1(chainID uint64, blockTime uint64, genesis *core.Genesis, c clock.Clock, blobPoolDir string, beaconSrv Beacon, opts ...GethOption) (*node.Node, *eth.Ethereum, error) {
ethConfig := &ethconfig.Config{ ethConfig := &ethconfig.Config{
NetworkId: chainID, NetworkId: chainID,
Genesis: genesis, Genesis: genesis,
BlobPool: blobpool.Config{
Datadir: blobPoolDir,
Datacap: blobpool.DefaultConfig.Datacap,
PriceBump: blobpool.DefaultConfig.PriceBump,
},
} }
nodeConfig := &node.Config{ nodeConfig := &node.Config{
Name: "l1-geth", Name: "l1-geth",
...@@ -53,6 +59,7 @@ func InitL1(chainID uint64, blockTime uint64, genesis *core.Genesis, c clock.Clo ...@@ -53,6 +59,7 @@ func InitL1(chainID uint64, blockTime uint64, genesis *core.Genesis, c clock.Clo
finalizedDistance: 8, finalizedDistance: 8,
safeDistance: 4, safeDistance: 4,
engineAPI: catalyst.NewConsensusAPI(l1Eth), engineAPI: catalyst.NewConsensusAPI(l1Eth),
beacon: beaconSrv,
}) })
return l1Node, l1Eth, nil return l1Node, l1Eth, nil
......
...@@ -24,6 +24,8 @@ type ExternalRunner struct { ...@@ -24,6 +24,8 @@ type ExternalRunner struct {
BinPath string BinPath string
Genesis *core.Genesis Genesis *core.Genesis
JWTPath string JWTPath string
// 4844: a datadir specifically for tx-pool blobs
BlobPoolPath string
} }
type ExternalEthClient struct { type ExternalEthClient struct {
......
...@@ -42,6 +42,7 @@ import ( ...@@ -42,6 +42,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
"github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth"
"github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-node/chaincfg"
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
...@@ -110,6 +111,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig { ...@@ -110,6 +111,7 @@ func DefaultSystemConfig(t *testing.T) SystemConfig {
L1InfoPredeployAddress: predeploys.L1BlockAddr, L1InfoPredeployAddress: predeploys.L1BlockAddr,
JWTFilePath: writeDefaultJWT(t), JWTFilePath: writeDefaultJWT(t),
JWTSecret: testingJWTSecret, JWTSecret: testingJWTSecret,
BlobsPath: t.TempDir(),
Nodes: map[string]*rollupNode.Config{ Nodes: map[string]*rollupNode.Config{
"sequencer": { "sequencer": {
Driver: driver.Config{ Driver: driver.Config{
...@@ -176,6 +178,8 @@ type SystemConfig struct { ...@@ -176,6 +178,8 @@ type SystemConfig struct {
JWTFilePath string JWTFilePath string
JWTSecret [32]byte JWTSecret [32]byte
BlobsPath string
Premine map[common.Address]*big.Int Premine map[common.Address]*big.Int
Nodes map[string]*rollupNode.Config // Per node config. Don't use populate rollup.Config Nodes map[string]*rollupNode.Config // Per node config. Don't use populate rollup.Config
Loggers map[string]log.Logger Loggers map[string]log.Logger
...@@ -260,6 +264,8 @@ type System struct { ...@@ -260,6 +264,8 @@ type System struct {
BatchSubmitter *bss.BatcherService BatchSubmitter *bss.BatcherService
Mocknet mocknet.Mocknet Mocknet mocknet.Mocknet
L1BeaconAPIAddr string
// TimeTravelClock is nil unless SystemConfig.SupportL1TimeTravel was set to true // TimeTravelClock is nil unless SystemConfig.SupportL1TimeTravel was set to true
// It provides access to the clock instance used by the L1 node. Calling TimeTravelClock.AdvanceBy // It provides access to the clock instance used by the L1 node. Calling TimeTravelClock.AdvanceBy
// allows tests to quickly time travel L1 into the future. // allows tests to quickly time travel L1 into the future.
...@@ -438,8 +444,19 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste ...@@ -438,8 +444,19 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste
} }
sys.RollupConfig = &defaultConfig sys.RollupConfig = &defaultConfig
// Create a fake Beacon node to hold on to blobs created by the L1 miner, and to serve them to L2
bcn := fakebeacon.NewBeacon(testlog.Logger(t, log.LvlInfo).New("role", "l1_cl"),
path.Join(cfg.BlobsPath, "l1_cl"), l1Genesis.Timestamp, cfg.DeployConfig.L1BlockTime)
t.Cleanup(func() {
_ = bcn.Close()
})
require.NoError(t, bcn.Start("127.0.0.1:0"))
beaconApiAddr := bcn.BeaconAddr()
require.NotEmpty(t, beaconApiAddr, "beacon API listener must be up")
// Initialize nodes // Initialize nodes
l1Node, l1Backend, err := geth.InitL1(cfg.DeployConfig.L1ChainID, cfg.DeployConfig.L1BlockTime, l1Genesis, c, cfg.GethOptions["l1"]...) l1Node, l1Backend, err := geth.InitL1(cfg.DeployConfig.L1ChainID, cfg.DeployConfig.L1BlockTime, l1Genesis, c,
path.Join(cfg.BlobsPath, "l1_el"), bcn, cfg.GethOptions["l1"]...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
package eth
import (
"crypto/sha256"
"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
}
// 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)
}
package eth
type BlobSidecar struct {
BlockRoot Bytes32 `json:"block_root"`
Slot Uint64String `json:"slot"`
Blob Blob `json:"blob"`
Index Uint64String `json:"index"`
KZGCommitment Bytes48 `json:"kzg_commitment"`
KZGProof Bytes48 `json:"kzg_proof"`
}
type APIGetBlobSidecarsResponse struct {
Data []*BlobSidecar `json:"data"`
}
type ReducedGenesisData struct {
GenesisTime Uint64String `json:"genesis_time"`
}
type APIGenesisResponse struct {
Data ReducedGenesisData `json:"data"`
}
type ReducedConfigData struct {
SecondsPerSlot Uint64String `json:"SECONDS_PER_SLOT"`
}
type APIConfigResponse struct {
Data ReducedConfigData `json:"data"`
}
...@@ -94,3 +94,11 @@ func (id L2BlockRef) ParentID() BlockID { ...@@ -94,3 +94,11 @@ func (id L2BlockRef) ParentID() BlockID {
Number: n, Number: n,
} }
} }
// IndexedDataHash represents a data-hash that commits to a single blob confirmed in a block.
// The index helps us avoid unnecessary blob to data-hash conversions to find the right content in a sidecar.
type IndexedDataHash struct {
Index uint64 // absolute index in the block, a.k.a. position in sidecar blobs array
DataHash common.Hash // hash of the blob, used for consistency checks
// Might add tx index and/or tx hash here later, depending on blobs API design
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"reflect" "reflect"
"strconv"
"github.com/ethereum/go-ethereum/beacon/engine" "github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
...@@ -317,3 +318,44 @@ type SystemConfig struct { ...@@ -317,3 +318,44 @@ type SystemConfig struct {
GasLimit uint64 `json:"gasLimit"` GasLimit uint64 `json:"gasLimit"`
// More fields can be added for future SystemConfig versions. // More fields can be added for future SystemConfig versions.
} }
type Bytes48 [48]byte
func (b *Bytes48) UnmarshalJSON(text []byte) error {
return hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), text, b[:])
}
func (b *Bytes48) UnmarshalText(text []byte) error {
return hexutil.UnmarshalFixedText("Bytes32", text, b[:])
}
func (b Bytes48) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}
func (b Bytes48) String() string {
return hexutil.Encode(b[:])
}
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
func (b Bytes48) TerminalString() string {
return fmt.Sprintf("%x..%x", b[:3], b[45:])
}
// Uint64String is a decimal string representation of an uint64, for usage in the Beacon API JSON encoding
type Uint64String uint64
func (v Uint64String) MarshalText() (out []byte, err error) {
out = strconv.AppendUint(out, uint64(v), 10)
return
}
func (v *Uint64String) UnmarshalText(b []byte) error {
n, err := strconv.ParseUint(string(b), 0, 64)
if err != nil {
return err
}
*v = Uint64String(n)
return nil
}
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