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

feat(op-e2e): Expose L1Replica + L2Engine + BlobsStore endpoints #11926

Merged
merged 5 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion op-e2e/actions/l1_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ func (s *L1Miner) ActL1EndBlock(t Testing) {
for _, sidecar := range s.l1BuildingBlobSidecars {
for i, h := range sidecar.BlobHashes() {
blob := (*eth.Blob)(&sidecar.Blobs[i])
s.blobStore.StoreBlob(block.Time(), h, blob)
indexedHash := eth.IndexedBlobHash{Index: uint64(i), Hash: h}
s.blobStore.StoreBlob(block.Time(), indexedHash, blob)
}
}
_, err = s.l1Chain.InsertChain(types.Blocks{block})
Expand Down
4 changes: 4 additions & 0 deletions op-e2e/actions/l1_replica.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ func (s *L1Replica) MockL1RPCErrors(fn func() error) {
}
}

func (s *L1Replica) HTTPEndpoint() string {
return s.node.HTTPEndpoint()
}

func (s *L1Replica) EthClient() *ethclient.Client {
cl := s.node.Attach()
return ethclient.NewClient(cl)
Expand Down
4 changes: 4 additions & 0 deletions op-e2e/actions/l2_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ func (s *L2Engine) PeerCount() int {
return s.node.Server().PeerCount()
}

func (s *L2Engine) HTTPEndpoint() string {
return s.node.HTTPEndpoint()
}

func (s *L2Engine) EthClient() *ethclient.Client {
cl := s.node.Attach()
return ethclient.NewClient(cl)
Expand Down
53 changes: 42 additions & 11 deletions op-e2e/e2eutils/blobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto/kzg4844"

"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
Expand All @@ -15,20 +14,20 @@ import (
// BlobsStore is a simple in-memory store of blobs, for testing purposes
type BlobsStore struct {
// block timestamp -> blob versioned hash -> blob
blobs map[uint64]map[common.Hash]*eth.Blob
blobs map[uint64]map[eth.IndexedBlobHash]*eth.Blob
}

func NewBlobStore() *BlobsStore {
return &BlobsStore{blobs: make(map[uint64]map[common.Hash]*eth.Blob)}
return &BlobsStore{blobs: make(map[uint64]map[eth.IndexedBlobHash]*eth.Blob)}
}

func (store *BlobsStore) StoreBlob(blockTime uint64, versionedHash common.Hash, blob *eth.Blob) {
func (store *BlobsStore) StoreBlob(blockTime uint64, indexedHash eth.IndexedBlobHash, blob *eth.Blob) {
m, ok := store.blobs[blockTime]
if !ok {
m = make(map[common.Hash]*eth.Blob)
m = make(map[eth.IndexedBlobHash]*eth.Blob)
store.blobs[blockTime] = m
}
m[versionedHash] = blob
m[indexedHash] = blob
}

func (store *BlobsStore) GetBlobs(ctx context.Context, ref eth.L1BlockRef, hashes []eth.IndexedBlobHash) ([]*eth.Blob, error) {
Expand All @@ -38,7 +37,7 @@ func (store *BlobsStore) GetBlobs(ctx context.Context, ref eth.L1BlockRef, hashe
return nil, fmt.Errorf("no blobs known with given time: %w", ethereum.NotFound)
}
for _, h := range hashes {
b, ok := m[h.Hash]
b, ok := m[h]
if !ok {
return nil, fmt.Errorf("blob %d %s is not in store: %w", h.Index, h.Hash, ethereum.NotFound)
}
Expand All @@ -54,7 +53,7 @@ func (store *BlobsStore) GetBlobSidecars(ctx context.Context, ref eth.L1BlockRef
return nil, fmt.Errorf("no blobs known with given time: %w", ethereum.NotFound)
}
for _, h := range hashes {
b, ok := m[h.Hash]
b, ok := m[h]
if !ok {
return nil, fmt.Errorf("blob %d %s is not in store: %w", h.Index, h.Hash, ethereum.NotFound)
}
Expand All @@ -66,15 +65,47 @@ func (store *BlobsStore) GetBlobSidecars(ctx context.Context, ref eth.L1BlockRef
if err != nil {
return nil, fmt.Errorf("failed to convert blob to commitment: %w", err)
}
proof, err := kzg4844.ComputeBlobProof(b.KZGBlob(), commitment)
if err != nil {
return nil, fmt.Errorf("failed to compute blob proof: %w", err)
}
out = append(out, &eth.BlobSidecar{
Index: eth.Uint64String(h.Index),
Blob: *b,
KZGCommitment: eth.Bytes48(commitment),
KZGProof: eth.Bytes48(proof),
})
}
return out, nil
}

var (
_ derive.L1BlobsFetcher = (*BlobsStore)(nil)
)
func (store *BlobsStore) GetAllSidecars(ctx context.Context, l1Timestamp uint64) ([]*eth.BlobSidecar, error) {
m, ok := store.blobs[l1Timestamp]
if !ok {
return nil, fmt.Errorf("no blobs known with given time: %w", ethereum.NotFound)
}
out := make([]*eth.BlobSidecar, len(m))
for h, b := range m {
if b == nil {
return nil, fmt.Errorf("blob %d %s is nil, cannot copy: %w", h.Index, h.Hash, ethereum.NotFound)
}

commitment, err := kzg4844.BlobToCommitment(b.KZGBlob())
if err != nil {
return nil, fmt.Errorf("failed to convert blob to commitment: %w", err)
}
proof, err := kzg4844.ComputeBlobProof(b.KZGBlob(), commitment)
if err != nil {
return nil, fmt.Errorf("failed to compute blob proof: %w", err)
}
out[h.Index] = &eth.BlobSidecar{
Index: eth.Uint64String(h.Index),
Blob: *b,
KZGCommitment: eth.Bytes48(commitment),
KZGProof: eth.Bytes48(proof),
}
}
return out, nil
}

var _ derive.L1BlobsFetcher = (*BlobsStore)(nil)
73 changes: 44 additions & 29 deletions op-e2e/e2eutils/fakebeacon/blobs.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
package fakebeacon

import (
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"

"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"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/common/hexutil"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
)

Expand All @@ -27,8 +28,8 @@ import (
type FakeBeacon struct {
log log.Logger

// directory to store blob contents in after the blobs are persisted in a block
blobsDir string
// in-memory blob store
blobStore *e2eutils.BlobsStore
blobsLock sync.Mutex

beaconSrv *http.Server
Expand All @@ -38,10 +39,10 @@ type FakeBeacon struct {
blockTime uint64
}

func NewBeacon(log log.Logger, blobsDir string, genesisTime uint64, blockTime uint64) *FakeBeacon {
func NewBeacon(log log.Logger, blobStore *e2eutils.BlobsStore, genesisTime uint64, blockTime uint64) *FakeBeacon {
return &FakeBeacon{
log: log,
blobsDir: blobsDir,
blobStore: blobStore,
genesisTime: genesisTime,
blockTime: blockTime,
}
Expand Down Expand Up @@ -158,40 +159,54 @@ func (f *FakeBeacon) Start(addr string) error {
}

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)

// Solve for the slot timestamp.
// slot = (timestamp - genesis) / slot_time
// timestamp = slot * slot_time + genesis
slotTimestamp := slot*f.blockTime + f.genesisTime

for i, b := range bundle.Blobs {
f.blobStore.StoreBlob(
slotTimestamp,
eth.IndexedBlobHash{
Index: uint64(i),
Hash: eth.KZGToVersionedHash(kzg4844.Commitment(bundle.Commitments[i])),
},
(*eth.Blob)(b[:]),
)
}
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))

// Solve for the slot timestamp.
// slot = (timestamp - genesis) / slot_time
// timestamp = slot * slot_time + genesis
slotTimestamp := slot*f.blockTime + f.genesisTime

// Load blobs from the store
blobs, err := f.blobStore.GetAllSidecars(context.Background(), slotTimestamp)
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)
}
return nil, fmt.Errorf("failed to load blobs from store: %w", 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)

// Convert blobs to the bundle
out := engine.BlobsBundleV1{
Commitments: make([]hexutil.Bytes, len(blobs)),
Proofs: make([]hexutil.Bytes, len(blobs)),
Blobs: make([]hexutil.Bytes, len(blobs)),
}
for _, b := range blobs {
out.Commitments[b.Index] = hexutil.Bytes(b.KZGCommitment[:])
out.Proofs[b.Index] = hexutil.Bytes(b.KZGProof[:])
out.Blobs[b.Index] = hexutil.Bytes(b.Blob[:])
}

return &out, nil
}

Expand Down
7 changes: 5 additions & 2 deletions op-e2e/l1_beacon_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"testing"

"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon"
"github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth"
Expand All @@ -19,7 +20,8 @@ func TestGetVersion(t *testing.T) {

l := testlog.Logger(t, log.LevelInfo)

beaconApi := fakebeacon.NewBeacon(l, t.TempDir(), uint64(0), uint64(0))
blobStore := e2eutils.NewBlobStore()
beaconApi := fakebeacon.NewBeacon(l, blobStore, uint64(0), uint64(0))
t.Cleanup(func() {
_ = beaconApi.Close()
})
Expand All @@ -38,7 +40,8 @@ func Test404NotFound(t *testing.T) {

l := testlog.Logger(t, log.LevelInfo)

beaconApi := fakebeacon.NewBeacon(l, t.TempDir(), uint64(0), uint64(12))
blobStore := e2eutils.NewBlobStore()
beaconApi := fakebeacon.NewBeacon(l, blobStore, uint64(0), uint64(12))
t.Cleanup(func() {
_ = beaconApi.Close()
})
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ func (cfg SystemConfig) Start(t *testing.T, _opts ...SystemConfigOption) (*Syste

// 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.LevelInfo).New("role", "l1_cl"),
path.Join(cfg.BlobsPath, "l1_cl"), l1Genesis.Timestamp, cfg.DeployConfig.L1BlockTime)
e2eutils.NewBlobStore(), l1Genesis.Timestamp, cfg.DeployConfig.L1BlockTime)
t.Cleanup(func() {
_ = bcn.Close()
})
Expand Down
Loading