Skip to content

Commit

Permalink
Add configurability for blob confirmation depth
Browse files Browse the repository at this point in the history
  • Loading branch information
teddyknox committed Jun 26, 2024
1 parent 00c1401 commit 53d95c3
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 61 deletions.
3 changes: 3 additions & 0 deletions .env.example.holesky
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ EIGENDA_PROXY_SERVICE_MANAGER_ADDR=0xD4A7E1Bd8015057293f0D0A557088c286942e84b
# Directory path to SRS tables
# EIGENDA_PROXY_TARGET_CACHE_PATH=resources/SRSTables

# The number of Ethereum blocks of confirmation that the DA briging transaction must have before it is assumed by the proxy to be final. The value of `0` indicates that the proxy should wait for weak-subjectivity finalization (12-14 minutes).
# EIGENDA_PROXY_ETH_CONFIRMATION_DEPTH=6

# Directory path to g1.point file
# EIGENDA_PROXY_TARGET_KZG_G1_PATH=resources/g1.point

Expand Down
3 changes: 3 additions & 0 deletions .env.example.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ EIGENDA_PROXY_SERVICE_MANAGER_ADDR=0x870679E138bCdf293b7Ff14dD44b70FC97e12fc0
# Directory path to SRS tables
# EIGENDA_PROXY_TARGET_CACHE_PATH=resources/SRSTables

# The number of Ethereum blocks of confirmation that the DA briging transaction must have before it is assumed by the proxy to be final. The value of `0` indicates that the proxy should wait for weak-subjectivity finalization (12-14 minutes).
# EIGENDA_PROXY_ETH_CONFIRMATION_DEPTH=6

# Directory path to g1.point file
# EIGENDA_PROXY_TARGET_KZG_G1_PATH=resources/g1.point

Expand Down
57 changes: 29 additions & 28 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions e2e/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func CreateTestSuite(t *testing.T, useMemory bool) (TestSuite, func()) {
PutBlobEncodingVersion: 0x00,
MemstoreEnabled: useMemory,
MemstoreBlobExpiration: 14 * 24 * time.Hour,
EthConfirmationDepth: 6,
}

store, err := server.LoadStore(
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/Layr-Labs/eigenda-proxy
go 1.21

require (
github.com/Layr-Labs/eigenda v0.7.2-0.20240606180508-e90cb7432ca5
github.com/Layr-Labs/eigenda v0.7.5-0.20240626225853-1645ffe3489e
github.com/consensys/gnark-crypto v0.12.1
github.com/ethereum-optimism/optimism v1.7.7
github.com/ethereum/go-ethereum v1.14.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Layr-Labs/eigenda v0.7.2-0.20240606180508-e90cb7432ca5 h1:PGcoSXnIlZYhwfrzqG1F2E/Sqc3ZGRqa5owryswax2s=
github.com/Layr-Labs/eigenda v0.7.2-0.20240606180508-e90cb7432ca5/go.mod h1:gG5KSp5gGY0lywj6aZwaK9ZEF8eEVX4ilo679pFpvAA=
github.com/Layr-Labs/eigenda v0.7.5-0.20240626225853-1645ffe3489e h1:vibQgDKVXvuKZKqdyvIuCx8MH1B5uOGrKITAUp3bzJg=
github.com/Layr-Labs/eigenda v0.7.5-0.20240626225853-1645ffe3489e/go.mod h1:gG5KSp5gGY0lywj6aZwaK9ZEF8eEVX4ilo679pFpvAA=
github.com/Layr-Labs/eigensdk-go v0.1.7-0.20240507215523-7e4891d5099a h1:L/UsJFw9M31FD/WgXTPFB0oxbq9Cu4Urea1xWPMQS7Y=
github.com/Layr-Labs/eigensdk-go v0.1.7-0.20240507215523-7e4891d5099a/go.mod h1:OF9lmS/57MKxS0xpSpX0qHZl0SKkDRpvJIvsGvMN1y8=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
Expand Down
23 changes: 17 additions & 6 deletions server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
EigenDADisperserRPCFlagName = "eigenda-disperser-rpc"
EthRPCFlagName = "eigenda-eth-rpc"
SvcManagerAddrFlagName = "eigenda-svc-manager-addr"
EthConfirmationDepthFlagName = "eigenda-eth-confirmation-depth"
StatusQueryRetryIntervalFlagName = "eigenda-status-query-retry-interval"
StatusQueryTimeoutFlagName = "eigenda-status-query-timeout"
DisableTlsFlagName = "eigenda-disable-tls"
Expand Down Expand Up @@ -50,8 +51,9 @@ type Config struct {
PutBlobEncodingVersion codecs.BlobEncodingVersion

// ETH vars
EthRPC string
SvcManagerAddr string
EthRPC string
SvcManagerAddr string
EthConfirmationDepth uint64

// KZG vars
CacheDir string
Expand Down Expand Up @@ -111,10 +113,11 @@ func (c *Config) VerificationCfg() *verify.Config {
}

return &verify.Config{
Verify: true,
RPCURL: c.EthRPC,
SvcManagerAddr: c.SvcManagerAddr,
KzgConfig: kzgCfg,
Verify: true,
RPCURL: c.EthRPC,
SvcManagerAddr: c.SvcManagerAddr,
KzgConfig: kzgCfg,
EthConfirmationDepth: c.EthConfirmationDepth,
}

}
Expand All @@ -140,9 +143,11 @@ func ReadConfig(ctx *cli.Context) Config {
MaxBlobLength: ctx.String(MaxBlobLengthFlagName),
SvcManagerAddr: ctx.String(SvcManagerAddrFlagName),
EthRPC: ctx.String(EthRPCFlagName),
EthConfirmationDepth: ctx.Uint64(EthConfirmationDepthFlagName),
MemstoreEnabled: ctx.Bool(MemstoreFlagName),
MemstoreBlobExpiration: ctx.Duration(MemstoreExpirationFlagName),
}
cfg.ClientConfig.WaitForFinalization = (cfg.EthConfirmationDepth != 0)
return cfg
}

Expand Down Expand Up @@ -245,6 +250,12 @@ func CLIFlags(envPrefix string) []cli.Flag {
Usage: "The deployed EigenDA service manager address. The list can be found here: https://github.com/Layr-Labs/eigenlayer-middleware/?tab=readme-ov-file#current-mainnet-deployment",
EnvVars: prefixEnvVars("SERVICE_MANAGER_ADDR"),
},
&cli.Uint64Flag{
Name: EthConfirmationDepthFlagName,
Usage: "The number of Ethereum blocks of confirmation that the DA briging transaction must have before it is assumed by the proxy to be final. The value of `0` indicates that the proxy should wait for weak-subjectivity finalization (12-14 minutes).",
EnvVars: prefixEnvVars("ETH_CONFIRMATION_DEPTH"),
Value: 6,
},
&cli.BoolFlag{
Name: MemstoreFlagName,
Usage: "Whether to use mem-store for DA logic.",
Expand Down
66 changes: 51 additions & 15 deletions server/eigenda_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,40 @@ package server

import (
"context"
"errors"
"fmt"
"time"

"github.com/Layr-Labs/eigenda-proxy/verify"
"github.com/Layr-Labs/eigenda/api/clients"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)

type EigenDAStoreConfig struct {
MaxBlobSizeBytes uint64
EthConfirmationDepth uint64

// The total amount of time that the client will spend waiting for EigenDA to confirm a blob
StatusQueryTimeout time.Duration
}

// EigenDAStore does storage interactions and verifications for blobs with DA.
type EigenDAStore struct {
client *clients.EigenDAClient
verifier *verify.Verifier
maxBlobSizeBytes uint64
client *clients.EigenDAClient
verifier *verify.Verifier
cfg *EigenDAStoreConfig
log log.Logger
}

var _ Store = (*EigenDAStore)(nil)

func NewEigenDAStore(ctx context.Context, client *clients.EigenDAClient, v *verify.Verifier, maxBlobSizeBytes uint64) (*EigenDAStore, error) {
func NewEigenDAStore(ctx context.Context, client *clients.EigenDAClient, v *verify.Verifier, log log.Logger, cfg *EigenDAStoreConfig) (*EigenDAStore, error) {
return &EigenDAStore{
client: client,
verifier: v,
maxBlobSizeBytes: maxBlobSizeBytes,
client: client,
verifier: v,
log: log,
cfg: cfg,
}, nil
}

Expand Down Expand Up @@ -50,11 +63,6 @@ func (e EigenDAStore) Get(ctx context.Context, key []byte, domain DomainType) ([
return nil, err
}

err = e.verifier.VerifyCert(&cert)
if err != nil {
return nil, err
}

switch domain {
case BinaryDomain:
return decodedBlob, nil
Expand All @@ -67,13 +75,16 @@ func (e EigenDAStore) Get(ctx context.Context, key []byte, domain DomainType) ([

// Put disperses a blob for some pre-image and returns the associated RLP encoded certificate commit.
func (e EigenDAStore) Put(ctx context.Context, value []byte) (comm []byte, err error) {
if uint64(len(value)) > e.maxBlobSizeBytes {
return nil, fmt.Errorf("blob is larger than max blob size: blob length %d, max blob size %d", len(value), e.maxBlobSizeBytes)
if uint64(len(value)) > e.cfg.MaxBlobSizeBytes {
return nil, fmt.Errorf("blob is larger than max blob size: blob length %d, max blob size %d", len(value), e.cfg.MaxBlobSizeBytes)
}
cert, err := e.client.PutBlob(ctx, value)

dispersalStart := time.Now()
blobInfo, err := e.client.PutBlob(ctx, value)
if err != nil {
return nil, err
}
cert := (*verify.Certificate)(blobInfo)

encodedBlob, err := e.client.GetCodec().EncodeBlob(value)
if err != nil {
Expand All @@ -84,6 +95,31 @@ func (e EigenDAStore) Put(ctx context.Context, value []byte) (comm []byte, err e
return nil, err
}

dispersalDuration := time.Since(dispersalStart)
remainingTimeout := e.cfg.StatusQueryTimeout - dispersalDuration

ticker := time.NewTicker(12 * time.Second)
defer ticker.Stop()
ctx, cancel := context.WithTimeout(context.Background(), remainingTimeout)
defer cancel()

done := false
for !done {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
err = e.verifier.VerifyCert(cert)
if err == nil {
done = true
} else if !errors.Is(err, verify.ErrBatchMetadataHashNotFound) {
return nil, err
} else {
e.log.Info("Blob confirmed, waiting for sufficient confirmation depth...", "targetDepth", e.cfg.EthConfirmationDepth)
}
}
}

bytes, err := rlp.EncodeToBytes(cert)
if err != nil {
return nil, fmt.Errorf("failed to encode DA cert to RLP format: %w", err)
Expand Down
7 changes: 6 additions & 1 deletion server/load_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func LoadStore(cfg CLIConfig, ctx context.Context, log log.Logger) (Store, error
ctx,
client,
verifier,
maxBlobLength,
log,
&EigenDAStoreConfig{
MaxBlobSizeBytes: maxBlobLength,
EthConfirmationDepth: cfg.EigenDAConfig.EthConfirmationDepth,
StatusQueryTimeout: cfg.EigenDAConfig.ClientConfig.StatusQueryTimeout,
},
)
}
49 changes: 45 additions & 4 deletions verify/cert.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
package verify

import (
"bytes"
"context"
"errors"
"fmt"
"math/big"

binding "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"

"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/exp/slices"
)

var ErrBatchMetadataHashNotFound = errors.New("BatchMetadataHash not found for BatchId")

// CertVerifier verifies the DA certificate against on-chain EigenDA contracts
// to ensure disperser returned fields haven't been tampered with
type CertVerifier struct {
manager *binding.ContractEigenDAServiceManagerCaller
ethConfirmationDepth uint64
manager *binding.ContractEigenDAServiceManagerCaller
finalizedBlockClient *FinalizedBlockClient
ethClient *ethclient.Client
}

func NewCertVerifier(cfg *Config, l log.Logger) (*CertVerifier, error) {
Expand All @@ -30,22 +40,33 @@ func NewCertVerifier(cfg *Config, l log.Logger) (*CertVerifier, error) {
}

return &CertVerifier{
manager: m,
manager: m,
finalizedBlockClient: NewFinalizedBlockClient(client.Client()),
ethConfirmationDepth: cfg.EthConfirmationDepth,
ethClient: client,
}, nil
}

func (cv *CertVerifier) VerifyBatch(header *binding.IEigenDAServiceManagerBatchHeader,
id uint32, recordHash [32]byte, blockNum uint32) error {
// 0 - Determine block context number
blockNumber, err := cv.getContextBlock()
if err != nil {
return err
}

// 1 - Verify batch hash

// 1.a - ensure that a batch hash can be looked up for a batch ID
expectedHash, err := cv.manager.BatchIdToBatchMetadataHash(nil, id)
expectedHash, err := cv.manager.BatchIdToBatchMetadataHash(&bind.CallOpts{BlockNumber: blockNumber}, id)
if err != nil {
return err
}
if bytes.Equal(expectedHash[:], make([]byte, 32)) {
return ErrBatchMetadataHashNotFound
}

// 1.b - ensure that hash generated from local cert matches one stored on-chain

actualHash, err := HashBatchMetadata(header, recordHash, blockNum)

if err != nil {
Expand Down Expand Up @@ -84,3 +105,23 @@ func (cv *CertVerifier) VerifyMerkleProof(inclusionProof []byte, root []byte, bl
func (cv *CertVerifier) VerifyBlobParams(inclusionProof []byte, rootHash []byte, leafHash []byte, index uint64) error {
return nil
}

func (cv *CertVerifier) getContextBlock() (*big.Int, error) {
var blockNumber *big.Int
if cv.ethConfirmationDepth == 0 {
// Get the latest finalized block
blockHeader, err := cv.finalizedBlockClient.GetBlock(context.Background(), "finalized", false)
if err != nil {
return nil, err
}
blockNumber = blockHeader.Number()
} else {
blockHeader, err := cv.ethClient.BlockByNumber(context.Background(), nil)
if err != nil {
return nil, err
}
blockNumber = new(big.Int)
blockNumber.Sub(blockHeader.Number(), big.NewInt(int64(cv.ethConfirmationDepth-1)))
}
return blockNumber, nil
}
63 changes: 63 additions & 0 deletions verify/finalized_block_number_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package verify

import (
"context"
"encoding/json"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
)

type FinalizedBlockClient struct {
c *rpc.Client
}

// Dial connects a client to the given URL.
func Dial(rawurl string) (*FinalizedBlockClient, error) {
return DialContext(context.Background(), rawurl)
}

// DialContext connects a client to the given URL with context.
func DialContext(ctx context.Context, rawurl string) (*FinalizedBlockClient, error) {
c, err := rpc.DialContext(ctx, rawurl)
if err != nil {
return nil, err
}
return NewFinalizedBlockClient(c), nil
}

// NewFinalizedBlockClient creates a client that uses the given RPC client.
func NewFinalizedBlockClient(c *rpc.Client) *FinalizedBlockClient {
return &FinalizedBlockClient{c}
}

// Close closes the underlying RPC connection.
func (ec *FinalizedBlockClient) Close() {
ec.c.Close()
}

// Client gets the underlying RPC client.
func (ec *FinalizedBlockClient) Client() *rpc.Client {
return ec.c
}

func (c *FinalizedBlockClient) GetBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) {
var raw json.RawMessage
err := c.c.CallContext(ctx, &raw, method, args...)
if err != nil {
return nil, err
}

// Decode header and transactions.
var head *types.Header
if err := json.Unmarshal(raw, &head); err != nil {
return nil, err
}
// When the block is not found, the API returns JSON null.
if head == nil {
return nil, ethereum.NotFound
}

return types.NewBlockWithHeader(head), nil
}
Loading

0 comments on commit 53d95c3

Please sign in to comment.