diff --git a/op-e2e/actions/l1_miner.go b/op-e2e/actions/l1_miner.go index e29d09ceb796..c35e38ae63c5 100644 --- a/op-e2e/actions/l1_miner.go +++ b/op-e2e/actions/l1_miner.go @@ -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}) diff --git a/op-e2e/actions/l1_replica.go b/op-e2e/actions/l1_replica.go index 83120203ee7b..3f6a27ed541f 100644 --- a/op-e2e/actions/l1_replica.go +++ b/op-e2e/actions/l1_replica.go @@ -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) diff --git a/op-e2e/actions/l2_engine.go b/op-e2e/actions/l2_engine.go index 828ede53cade..80d705d854e5 100644 --- a/op-e2e/actions/l2_engine.go +++ b/op-e2e/actions/l2_engine.go @@ -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) diff --git a/op-e2e/e2eutils/blobs.go b/op-e2e/e2eutils/blobs.go index ae9ccd562c02..791130470eda 100644 --- a/op-e2e/e2eutils/blobs.go +++ b/op-e2e/e2eutils/blobs.go @@ -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" @@ -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) { @@ -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) } @@ -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) } @@ -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, ð.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] = ð.BlobSidecar{ + Index: eth.Uint64String(h.Index), + Blob: *b, + KZGCommitment: eth.Bytes48(commitment), + KZGProof: eth.Bytes48(proof), + } + } + return out, nil +} + +var _ derive.L1BlobsFetcher = (*BlobsStore)(nil) diff --git a/op-e2e/e2eutils/fakebeacon/blobs.go b/op-e2e/e2eutils/fakebeacon/blobs.go index 6be65bbb3521..a96042bc7add 100644 --- a/op-e2e/e2eutils/fakebeacon/blobs.go +++ b/op-e2e/e2eutils/fakebeacon/blobs.go @@ -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" ) @@ -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 @@ -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, } @@ -158,20 +159,23 @@ 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 } @@ -179,19 +183,30 @@ func (f *FakeBeacon) StoreBlobsBundle(slot uint64, bundle *engine.BlobsBundleV1) 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 } diff --git a/op-e2e/l1_beacon_client_test.go b/op-e2e/l1_beacon_client_test.go index 93094d4884e7..6d4ffd6207fa 100644 --- a/op-e2e/l1_beacon_client_test.go +++ b/op-e2e/l1_beacon_client_test.go @@ -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" @@ -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() }) @@ -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() }) diff --git a/op-e2e/setup.go b/op-e2e/setup.go index ab2218b097d4..dd99e8684040 100644 --- a/op-e2e/setup.go +++ b/op-e2e/setup.go @@ -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() })