Skip to content

Commit

Permalink
feat(op-e2e): Expose L1Replica + L2Engine + BlobsStore endpoints (
Browse files Browse the repository at this point in the history
#11926)

* feat(op-e2e): Expose `L1Replica` + `L2Engine` + `BlobsStore` endpoints

* mutex

* deterministic blob indexing

* proto review

* lint
  • Loading branch information
clabby authored Sep 16, 2024
1 parent d4467a1 commit 8dd6fb3
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 44 deletions.
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 @@ -588,7 +588,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

0 comments on commit 8dd6fb3

Please sign in to comment.