diff --git a/ethstorage/blobs/blob_reader.go b/ethstorage/blobs/blob_reader.go index f18d460c..5368ead7 100644 --- a/ethstorage/blobs/blob_reader.go +++ b/ethstorage/blobs/blob_reader.go @@ -13,7 +13,7 @@ import ( type BlobCacheReader interface { GetKeyValueByIndex(index uint64, hash common.Hash) []byte - GetKeyValueByIndexUnchecked(index uint64) []byte + GetSampleData(kvIndex, sampleIndexInKv uint64) []byte } // BlobReader provides unified interface for the miner to read blobs and samples @@ -53,13 +53,9 @@ func (n *BlobReader) GetBlob(kvIdx uint64, kvHash common.Hash) ([]byte, error) { func (n *BlobReader) ReadSample(shardIdx, sampleIdx uint64) (common.Hash, error) { sampleLenBits := n.sm.MaxKvSizeBits() - es.SampleSizeBits kvIdx := sampleIdx >> sampleLenBits - // get blob without checking commit since kvHash is not available - if blob := n.cr.GetKeyValueByIndexUnchecked(kvIdx); blob != nil { - n.lg.Debug("Loaded blob from downloader cache", "kvIdx", kvIdx) - sampleIdxInKv := sampleIdx % (1 << sampleLenBits) - sampleSize := uint64(1 << es.SampleSizeBits) - sampleIdxByte := sampleIdxInKv << es.SampleSizeBits - sample := blob[sampleIdxByte : sampleIdxByte+sampleSize] + sampleIdxInKv := sampleIdx % (1 << sampleLenBits) + + if sample := n.cr.GetSampleData(kvIdx, sampleIdxInKv); sample != nil { return common.BytesToHash(sample), nil } diff --git a/ethstorage/downloader/blob_cache_test.go b/ethstorage/downloader/blob_cache_test.go index 613ad98c..49772e97 100644 --- a/ethstorage/downloader/blob_cache_test.go +++ b/ethstorage/downloader/blob_cache_test.go @@ -7,39 +7,97 @@ import ( "bytes" "fmt" "math/big" + "math/rand" "os" + "path/filepath" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethstorage/go-ethstorage/ethstorage" + "github.com/ethstorage/go-ethstorage/ethstorage/log" "github.com/protolambda/go-kzg/eth" ) var ( - bc BlobCache + cache BlobCache kvHashes []common.Hash + datadir string fileName = "test_shard_0.dat" blobData = "blob data of kvIndex %d" + sampleLen = blobSize / sampleSize minerAddr = common.BigToAddress(common.Big1) kvSize uint64 = 1 << 17 kvEntries uint64 = 16 shardID = uint64(0) ) -func init() { - bc = NewBlobMemCache() +func TestDiskBlobCache(t *testing.T) { + setup(t) + t.Cleanup(func() { + teardown(t) + }) + + block, err := newBlockBlobs(10, 4) + if err != nil { + t.Fatalf("Failed to create new block blobs: %v", err) + } + + err = cache.SetBlockBlobs(block) + if err != nil { + t.Fatalf("Failed to set block blobs: %v", err) + } + + blobs := cache.Blobs(block.number) + if len(blobs) != len(block.blobs) { + t.Fatalf("Unexpected number of blobs: got %d, want %d", len(blobs), len(block.blobs)) + } + + for i, blob := range block.blobs { + blobData := cache.GetKeyValueByIndex(uint64(i), blob.hash) + if !bytes.Equal(blobData, blob.data) { + t.Fatalf("Unexpected blob data at index %d: got %x, want %x", i, blobData, blob.data) + } + } + + cache.Cleanup(5) + blobsAfterCleanup := cache.Blobs(block.number) + if len(blobsAfterCleanup) != len(block.blobs) { + t.Fatalf("Unexpected number of blobs after cleanup: got %d, want %d", len(blobsAfterCleanup), len(block.blobs)) + } + + block, err = newBlockBlobs(20, 6) + if err != nil { + t.Fatalf("Failed to create new block blobs: %v", err) + } + + err = cache.SetBlockBlobs(block) + if err != nil { + t.Fatalf("Failed to set block blobs: %v", err) + } + + cache.Cleanup(15) + blobsAfterCleanup = cache.Blobs(block.number) + if len(blobsAfterCleanup) != len(block.blobs) { + t.Fatalf("Unexpected number of blobs after cleanup: got %d, want %d", len(blobsAfterCleanup), len(block.blobs)) + } } -func TestBlobCache_Encoding(t *testing.T) { +func TestEncoding(t *testing.T) { + setup(t) + t.Cleanup(func() { + teardown(t) + }) + blockBlobsParams := []struct { blockNum uint64 blobLen uint64 }{ {0, 1}, + {1000, 4}, {1, 5}, {222, 6}, - {1000, 4}, {12345, 2}, {2000000, 3}, } @@ -66,14 +124,16 @@ func TestBlobCache_Encoding(t *testing.T) { for i, b := range bb.blobs { bb.blobs[i].data = sm.EncodeBlob(b.data, b.hash, b.kvIndex.Uint64(), kvSize) } - bc.SetBlockBlobs(bb) + if err := cache.SetBlockBlobs(bb); err != nil { + t.Fatalf("failed to set block blobs: %v", err) + } } // load from cache and verify for i, kvHash := range kvHashes { kvIndex := uint64(i) t.Run(fmt.Sprintf("test kv: %d", i), func(t *testing.T) { - blobEncoded := bc.GetKeyValueByIndexUnchecked(kvIndex) + blobEncoded := cache.GetKeyValueByIndex(kvIndex, kvHash) blobDecoded := sm.DecodeBlob(blobEncoded, kvHash, kvIndex, kvSize) bytesWant := []byte(fmt.Sprintf(blobData, kvIndex)) if !bytes.Equal(blobDecoded[:len(bytesWant)], bytesWant) { @@ -83,10 +143,83 @@ func TestBlobCache_Encoding(t *testing.T) { } } +func TestBlobDiskCache_GetSampleData(t *testing.T) { + setup(t) + t.Cleanup(func() { + teardown(t) + }) + + const blockStart = 10000000 + rand.New(rand.NewSource(time.Now().UnixNano())) + kvIndex2BlockNumber := map[uint64]uint64{} + kvIndex2BlobIndex := map[uint64]uint64{} + + newBlockBlobsFilled := func(blockNumber, blobLen uint64) (*blockBlobs, error) { + block := &blockBlobs{ + number: blockNumber, + blobs: make([]*blob, blobLen), + } + for i := uint64(0); i < blobLen; i++ { + kvIndex := uint64(len(kvHashes)) + blob := &blob{ + kvIndex: new(big.Int).SetUint64(kvIndex), + data: fill(blockNumber, i), + } + kzgBlob := kzg4844.Blob{} + copy(kzgBlob[:], blob.data) + commitment, err := kzg4844.BlobToCommitment(kzgBlob) + if err != nil { + return nil, fmt.Errorf( + "failed to create commitment for blob %d: %w", kvIndex, err) + } + blob.hash = common.Hash(eth.KZGToVersionedHash(eth.KZGCommitment(commitment))) + block.blobs[i] = blob + kvHashes = append(kvHashes, blob.hash) + kvIndex2BlockNumber[kvIndex] = blockNumber + kvIndex2BlobIndex[kvIndex] = i + } + t.Log("Block created", "number", block.number, "blobs", blobLen) + return block, nil + } + for i := 0; i < 10; i++ { + blockn, blobn := blockStart+i, rand.Intn(6)+1 + block, err := newBlockBlobsFilled(uint64(blockn), uint64(blobn)) + if err != nil { + t.Fatalf("Failed to create new block blobs: %v", err) + } + if err := cache.SetBlockBlobs(block); err != nil { + t.Fatalf("Failed to set block blobs: %v", err) + } + } + + for kvi := range kvHashes { + kvIndex := uint64(kvi) + sampleIndex := rand.Intn(int(sampleLen)) + sample := cache.GetSampleData(kvIndex, uint64(sampleIndex)) + sampleWant := make([]byte, sampleSize) + copy(sampleWant, fmt.Sprintf("%d_%d_%d", kvIndex2BlockNumber[kvIndex], kvIndex2BlobIndex[kvIndex], sampleIndex)) + t.Run(fmt.Sprintf("test sample: kvIndex=%d, sampleIndex=%d", kvIndex, sampleIndex), func(t *testing.T) { + if !bytes.Equal(sample, sampleWant) { + t.Errorf("GetSampleData got %x, want %x", sample, sampleWant) + } + }) + } + +} + +func fill(blockNumber, blobIndex uint64) []byte { + var content []byte + for i := uint64(0); i < sampleLen; i++ { + sample := make([]byte, sampleSize) + copy(sample, fmt.Sprintf("%d_%d_%d", blockNumber, blobIndex, i)) + content = append(content, sample...) + } + return content +} + func newBlockBlobs(blockNumber, blobLen uint64) (*blockBlobs, error) { block := &blockBlobs{ number: blockNumber, - hash: common.BigToHash(new(big.Int).SetUint64(blockNumber)), blobs: make([]*blob, blobLen), } for i := uint64(0); i < blobLen; i++ { @@ -109,3 +242,30 @@ func newBlockBlobs(blockNumber, blobLen uint64) (*blockBlobs, error) { } return block, nil } + +func setup(t *testing.T) { + // cache = NewBlobMemCache() + tmpDir := t.TempDir() + datadir = filepath.Join(tmpDir, "datadir") + err := os.MkdirAll(datadir, 0700) + if err != nil { + t.Fatalf("Failed to create datadir: %v", err) + } + t.Logf("datadir %s", datadir) + cache = NewBlobDiskCache(datadir, log.NewLogger(log.CLIConfig{ + Level: "warn", + Format: "text", + })) +} + +func teardown(t *testing.T) { + err := cache.Close() + if err != nil { + t.Errorf("Failed to close BlobCache: %v", err) + } + err = os.RemoveAll(datadir) + if err != nil { + t.Errorf("Failed to remove datadir: %v", err) + } + kvHashes = nil +} diff --git a/ethstorage/downloader/blob_disk_cache.go b/ethstorage/downloader/blob_disk_cache.go new file mode 100644 index 00000000..df0f3ff4 --- /dev/null +++ b/ethstorage/downloader/blob_disk_cache.go @@ -0,0 +1,209 @@ +// Copyright 2022-2023, EthStorage. +// For license information, see https://github.com/ethstorage/es-node/blob/main/LICENSE + +package downloader + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethstorage/billy" + "github.com/ethstorage/go-ethstorage/ethstorage" +) + +const ( + itemHeaderSize = 4 // size of the per-item header of billy + sampleSize = uint64(1 << ethstorage.SampleSizeBits) + blobSize = params.BlobTxFieldElementsPerBlob * params.BlobTxBytesPerFieldElement + blobCacheDir = "cached_blobs" +) + +type BlobDiskCache struct { + store billy.Database + storePath string + blockLookup map[uint64]*blockBlobs // Lookup table mapping block number to blockBlob + kvIndexLookup map[uint64]uint64 // Lookup table mapping kvIndex to blob billy entries id + mu sync.RWMutex // protects lookup and index maps + lg log.Logger +} + +func NewBlobDiskCache(datadir string, lg log.Logger) *BlobDiskCache { + cbdir := filepath.Join(datadir, blobCacheDir) + if err := os.MkdirAll(cbdir, 0700); err != nil { + lg.Crit("Failed to create cache directory", "dir", cbdir, "err", err) + } + c := &BlobDiskCache{ + blockLookup: make(map[uint64]*blockBlobs), + kvIndexLookup: make(map[uint64]uint64), + storePath: cbdir, + lg: lg, + } + + store, err := billy.Open(billy.Options{Path: cbdir, Repair: true}, newSlotter(), nil) + if err != nil { + lg.Crit("Failed to open cache directory", "dir", cbdir, "err", err) + } + c.store = store + + lg.Info("BlobDiskCache initialized", "dir", cbdir) + return c +} + +func (c *BlobDiskCache) SetBlockBlobs(block *blockBlobs) error { + c.mu.Lock() + defer c.mu.Unlock() + + if blockOld, ok := c.blockLookup[block.number]; ok { + for _, b := range blockOld.blobs { + if err := c.store.Delete(b.dataId); err != nil { + c.lg.Warn("Failed to delete blob from cache", "kvIndex", b.kvIndex, "id", b.dataId, "err", err) + } + } + } + var blbs []*blob + for _, b := range block.blobs { + kvi := b.kvIndex.Uint64() + id, err := c.store.Put(b.data) + if err != nil { + c.lg.Error("Failed to put blob into cache", "block", block.number, "kvIndex", kvi, "err", err) + return err + } + c.kvIndexLookup[kvi] = id + blbs = append(blbs, &blob{ + kvIndex: b.kvIndex, + kvSize: b.kvSize, + hash: b.hash, + dataId: id, + }) + } + c.blockLookup[block.number] = &blockBlobs{ + timestamp: block.timestamp, + number: block.number, + blobs: blbs, + } + c.lg.Info("Set blockBlobs to cache", "block", block.number) + return nil +} + +func (c *BlobDiskCache) Blobs(number uint64) []blob { + c.mu.RLock() + bb, ok := c.blockLookup[number] + c.mu.RUnlock() + if !ok { + return nil + } + c.lg.Info("Blobs from cache", "block", bb.number) + res := []blob{} + for _, blb := range bb.blobs { + data, err := c.store.Get(blb.dataId) + if err != nil { + c.lg.Error("Failed to get blockBlobs from storage", "block", number, "err", err) + return nil + } + res = append(res, blob{ + kvIndex: blb.kvIndex, + kvSize: blb.kvSize, + hash: blb.hash, + data: data, + }) + } + return res +} + +func (c *BlobDiskCache) GetKeyValueByIndex(idx uint64, hash common.Hash) []byte { + c.mu.RLock() + defer c.mu.RUnlock() + + for _, bb := range c.blockLookup { + for _, b := range bb.blobs { + if b.kvIndex.Uint64() == idx && + bytes.Equal(b.hash[0:ethstorage.HashSizeInContract], hash[0:ethstorage.HashSizeInContract]) { + data, err := c.store.Get(b.dataId) + if err != nil { + c.lg.Error("Failed to get kv from downloader cache", "kvIndex", idx, "id", b.dataId, "err", err) + return nil + } + return data + } + } + } + return nil +} + +func (c *BlobDiskCache) GetSampleData(idx, sampleIdx uint64) []byte { + c.mu.RLock() + id, ok := c.kvIndexLookup[idx] + c.mu.RUnlock() + if !ok { + return nil + } + + off := sampleIdx << ethstorage.SampleSizeBits + data, err := c.store.GetSample(id, off, sampleSize) + if err != nil { + c.lg.Error("Failed to get sample from downloader cache", "kvIndex", idx, "sampleIndex", sampleIdx, "id", id, "err", err) + return nil + } + return data +} + +func (c *BlobDiskCache) Cleanup(finalized uint64) { + start := time.Now() + defer func() { + c.lg.Info("BlobDiskCache cleanup done", "took", time.Since(start)) + }() + c.mu.Lock() + defer c.mu.Unlock() + + var blocksCleaned, blobsCleaned int + for number, block := range c.blockLookup { + if number <= finalized { + delete(c.blockLookup, number) + for _, blob := range block.blobs { + if blob.kvIndex != nil { + delete(c.kvIndexLookup, blob.kvIndex.Uint64()) + } + if err := c.store.Delete(blob.dataId); err != nil { + c.lg.Error("Failed to delete block from id", "id", blob.dataId, "err", err) + } + blobsCleaned++ + } + blocksCleaned++ + } + } + c.lg.Info("Cleanup done", "blockFinalized", finalized, "blocksCleaned", blocksCleaned, "blobsCleaned", blobsCleaned) +} + +func (c *BlobDiskCache) Close() error { + var er error + if err := c.store.Close(); err != nil { + c.lg.Error("Failed to close cache", "err", err) + er = err + } + if err := os.RemoveAll(c.storePath); err != nil { + c.lg.Error("Failed to remove cache dir", "err", err) + if er == nil { + er = err + } else { + er = errors.New(er.Error() + "; " + err.Error()) + } + } + if er != nil { + return er + } + c.lg.Info("BlobDiskCache closed.") + return nil +} + +func newSlotter() func() (uint32, bool) { + return func() (size uint32, done bool) { + return blobSize + itemHeaderSize, true + } +} diff --git a/ethstorage/downloader/blob_cache.go b/ethstorage/downloader/blob_mem_cache.go similarity index 70% rename from ethstorage/downloader/blob_cache.go rename to ethstorage/downloader/blob_mem_cache.go index 7a2070b9..f6254b31 100644 --- a/ethstorage/downloader/blob_cache.go +++ b/ethstorage/downloader/blob_mem_cache.go @@ -13,32 +13,33 @@ import ( ) type BlobMemCache struct { - blocks map[common.Hash]*blockBlobs + blocks map[uint64]*blockBlobs mu sync.RWMutex } func NewBlobMemCache() *BlobMemCache { return &BlobMemCache{ - blocks: map[common.Hash]*blockBlobs{}, + blocks: map[uint64]*blockBlobs{}, } } -func (c *BlobMemCache) SetBlockBlobs(block *blockBlobs) { +func (c *BlobMemCache) SetBlockBlobs(block *blockBlobs) error { c.mu.Lock() defer c.mu.Unlock() - c.blocks[block.hash] = block + c.blocks[block.number] = block + return nil } -func (c *BlobMemCache) Blobs(hash common.Hash) []blob { +func (c *BlobMemCache) Blobs(number uint64) []blob { c.mu.RLock() defer c.mu.RUnlock() - if _, exist := c.blocks[hash]; !exist { + if _, exist := c.blocks[number]; !exist { return nil } res := []blob{} - for _, blob := range c.blocks[hash].blobs { + for _, blob := range c.blocks[number].blobs { res = append(res, *blob) } return res @@ -58,14 +59,17 @@ func (c *BlobMemCache) GetKeyValueByIndex(idx uint64, hash common.Hash) []byte { return nil } -func (c *BlobMemCache) GetKeyValueByIndexUnchecked(idx uint64) []byte { +func (c *BlobMemCache) GetSampleData(idx, sampleIdxInKv uint64) []byte { c.mu.RLock() defer c.mu.RUnlock() for _, block := range c.blocks { for _, blob := range block.blobs { if blob.kvIndex.Uint64() == idx { - return blob.data + sampleSize := uint64(1 << ethstorage.SampleSizeBits) + sampleIdxByte := sampleIdxInKv << ethstorage.SampleSizeBits + sample := blob.data[sampleIdxByte : sampleIdxByte+sampleSize] + return sample } } } @@ -85,3 +89,8 @@ func (c *BlobMemCache) Cleanup(finalized uint64) { } } } + +func (c *BlobMemCache) Close() error { + c.blocks = nil + return nil +} diff --git a/ethstorage/downloader/downloader.go b/ethstorage/downloader/downloader.go index f498436b..454b74fb 100644 --- a/ethstorage/downloader/downloader.go +++ b/ethstorage/downloader/downloader.go @@ -39,11 +39,12 @@ var ( ) type BlobCache interface { - SetBlockBlobs(block *blockBlobs) - Blobs(hash common.Hash) []blob + SetBlockBlobs(block *blockBlobs) error + Blobs(number uint64) []blob GetKeyValueByIndex(idx uint64, hash common.Hash) []byte - GetKeyValueByIndexUnchecked(idx uint64) []byte + GetSampleData(idx uint64, sampleIdx uint64) []byte Cleanup(finalized uint64) + Close() error } type Downloader struct { @@ -78,15 +79,23 @@ type blob struct { kvSize *big.Int hash common.Hash data []byte + dataId uint64 +} + +func (b *blob) String() string { + return fmt.Sprintf("blob{kvIndex: %d, hash: %x, data: %s}", b.kvIndex, b.hash, b.data) } type blockBlobs struct { timestamp uint64 number uint64 - hash common.Hash blobs []*blob } +func (b *blockBlobs) String() string { + return fmt.Sprintf("blockBlobs{number: %d, timestamp: %d, blobs: %d}", b.number, b.timestamp, len(b.blobs)) +} + func NewDownloader( l1Source *eth.PollingClient, l1Beacon *eth.BeaconClient, @@ -332,7 +341,7 @@ func (s *Downloader) downloadRange(start int64, end int64, toCache bool) ([]blob blobs := []blob{} for _, elBlock := range elBlocks { // attempt to read the blobs from the cache first - res := s.Cache.Blobs(elBlock.hash) + res := s.Cache.Blobs(elBlock.number) if res != nil { blobs = append(blobs, res...) s.log.Info("Blob found in the cache, continue to the next block", "blockNumber", elBlock.number) @@ -378,10 +387,13 @@ func (s *Downloader) downloadRange(start int64, end int64, toCache bool) ([]blob // encode blobs so that miner can do sampling directly from cache elBlob.data = s.sm.EncodeBlob(clBlob.Data, elBlob.hash, elBlob.kvIndex.Uint64(), s.sm.MaxKvSize()) blobs = append(blobs, *elBlob) - s.log.Info("Download range", "blockNumber", elBlock.number, "kvIdx", elBlob.kvIndex) + s.log.Info("Downloaded and encoded", "blockNumber", elBlock.number, "kvIdx", elBlob.kvIndex) } if toCache { - s.Cache.SetBlockBlobs(elBlock) + if err := s.Cache.SetBlockBlobs(elBlock); err != nil { + s.log.Error("Failed to cache blobs", "block", elBlock.number, "err", err) + return nil, err + } } } @@ -424,7 +436,6 @@ func (s *Downloader) eventsToBlocks(events []types.Log) ([]*blockBlobs, error) { blocks = append(blocks, &blockBlobs{ timestamp: res.Time, number: event.BlockNumber, - hash: event.BlockHash, blobs: []*blob{}, }) } diff --git a/ethstorage/node/node.go b/ethstorage/node/node.go index 8f13c216..5ec2af41 100644 --- a/ethstorage/node/node.go +++ b/ethstorage/node/node.go @@ -41,12 +41,12 @@ type EsNode struct { l1FinalizedSub ethereum.Subscription // Subscription to get L1 Finalized blocks, a.k.a. justified data (polling) randaoHeadsSub ethereum.Subscription // Subscription to get randao heads (automatically re-subscribes on error) - randaoSource *eth.RandaoClient // RPC client to fetch randao from - l1Source *eth.PollingClient // L1 Client to fetch data from - l1Beacon *eth.BeaconClient // L1 Beacon Chain to fetch blobs from - daClient *eth.DAClient // L1 Data Availability Client - blobCache *downloader.BlobMemCache // Cache for blobs - downloader *downloader.Downloader // L2 Engine to Sync + randaoSource *eth.RandaoClient // RPC client to fetch randao from + l1Source *eth.PollingClient // L1 Client to fetch data from + l1Beacon *eth.BeaconClient // L1 Beacon Chain to fetch blobs from + daClient *eth.DAClient // L1 Data Availability Client + blobCache downloader.BlobCache // Cache for blobs + downloader *downloader.Downloader // L2 Engine to Sync // l2Source *sources.EngineClient // L2 Execution Engine RPC bindings // rpcSync *sources.SyncClient // Alt-sync RPC client, optional (may be nil) server *rpcServer // RPC server hosting the rollup-node API @@ -136,7 +136,7 @@ func (n *EsNode) init(ctx context.Context, cfg *Config) error { } func (n *EsNode) initL2(ctx context.Context, cfg *Config) error { - n.blobCache = downloader.NewBlobMemCache() + n.blobCache = downloader.NewBlobDiskCache(cfg.DataDir, n.log) n.downloader = downloader.NewDownloader( n.l1Source, n.l1Beacon, @@ -485,7 +485,6 @@ func (n *EsNode) Close() error { result = multierror.Append(result, fmt.Errorf("failed to close p2p node: %w", err)) } } - if n.downloader != nil { if err := n.downloader.Close(); err != nil { result = multierror.Append(result, fmt.Errorf("failed to close downloader: %w", err)) @@ -511,6 +510,11 @@ func (n *EsNode) Close() error { if n.miner != nil { n.miner.Close() } + if n.blobCache != nil { + if err := n.blobCache.Close(); err != nil { + result = multierror.Append(result, fmt.Errorf("failed to close blob cache: %w", err)) + } + } if n.archiverAPI != nil { n.archiverAPI.Stop(context.Background()) diff --git a/go.mod b/go.mod index 932ee4c2..6cec0395 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/optimism v1.2.0 github.com/ethereum/go-ethereum v1.13.5 + github.com/ethstorage/billy v0.0.0-20240730021803-ca24378685e7 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/gorilla/mux v1.8.1 github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d diff --git a/go.sum b/go.sum index b807f7b8..06d1e979 100644 --- a/go.sum +++ b/go.sum @@ -162,6 +162,8 @@ github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-ethereum v1.13.5 h1:U6TCRciCqZRe4FPXmy1sMGxTfuk8P7u2UoinF3VbaFk= github.com/ethereum/go-ethereum v1.13.5/go.mod h1:yMTu38GSuyxaYzQMViqNmQ1s3cE84abZexQmTgenWk0= +github.com/ethstorage/billy v0.0.0-20240730021803-ca24378685e7 h1:wOhbhs4WO8Mf0LT9c2qRxUbspAU9Tg4olLiastQTO5A= +github.com/ethstorage/billy v0.0.0-20240730021803-ca24378685e7/go.mod h1:y+M5PibhfdCZkQbwwFRIRIgGVc1txfqc3qvBeoQGoGw= github.com/ethstorage/go-iden3-crypto v0.0.0-20230406080944-d89aec086425 h1:dKQu1oXrt6ndFl4XtAZsfBubU8c5W59T85L8MGtWawE= github.com/ethstorage/go-iden3-crypto v0.0.0-20230406080944-d89aec086425/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBeOT/3UEhXsEsP3E= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=