diff --git a/.env.example.holesky b/.env.example.holesky
index 55706ed2..210ab4b7 100644
--- a/.env.example.holesky
+++ b/.env.example.holesky
@@ -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
diff --git a/.env.example.mainnet b/.env.example.mainnet
index 8cab2dab..53b016c9 100644
--- a/.env.example.mainnet
+++ b/.env.example.mainnet
@@ -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
diff --git a/README.md b/README.md
index 8badd63a..0a7d26e7 100644
--- a/README.md
+++ b/README.md
@@ -17,34 +17,35 @@ In order to disperse to the EigenDA network in production, or at high throughput
## Configuration Options
-| CLI Flag Name | Env Var Flag Name | Input Type | Default Value | Required | Description |
-|----------------------------------------------|----------------------------------------------|------------|---------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `--eigenda-rpc` | `EIGENDA_PROXY_RPC` | string | None | Yes | RPC host of the EigenDA disperser service (e.g., on Holesky this is `disperser-holesky.eigenda.xyz:443`). Full network list available in the documentation. |
-| `--eigenda-signer-private-key-hex` | `EIGENDA_PROXY_SIGNER_PRIVATE_KEY_HEX` | string | None | Yes | Hex-encoded signer private key. This key should not be associated with an Ethereum address holding any funds. |
-| `--eigenda-eth-rpc` | `EIGENDA_PROXY_ETH_RPC` | string | None | Yes | JSON RPC node endpoint for the Ethereum network used for finalizing DA blobs. See available list here: |
-| `--eigenda-svc-manager-addr` | `EIGENDA_PROXY_SERVICE_MANAGER_ADDR` | string | None | Yes | The deployed EigenDA service manager address. The list can be found here: |
-| `--eigenda-g1-path` | `EIGENDA_PROXY_TARGET_KZG_G1_PATH` | string | None | Yes | Directory path to g1.point file. |
-| `--eigenda-g2-tau-path` | `EIGENDA_PROXY_TARGET_G2_TAU_PATH` | string | None | Yes | Directory path to g2.point.powerOf2 file. |
-| `--eigenda-cache-path` | `EIGENDA_PROXY_TARGET_CACHE_PATH` | string | None | Yes | Directory path to SRS tables for caching. |
-| `--addr` | `EIGENDA_PROXY_ADDR` | string | "127.0.0.1" | No | Server listening address. |
-| `--port` | `EIGENDA_PROXY_PORT` | int | 3100 | No | Server listening port. |
-| `--eigenda-disable-tls` | `EIGENDA_PROXY_GRPC_DISABLE_TLS` | bool | false | No | Disable TLS for gRPC communication with the EigenDA disperser. |
-| `--eigenda-custom-quorum-ids` | `EIGENDA_PROXY_CUSTOM_QUORUM_IDS` | string | None | No | Custom quorum IDs for writing blobs. Should not include default quorums 0 or 1. |
-| `--eigenda-disable-point-verification-mode` | `EIGENDA_PROXY_DISABLE_POINT_VERIFICATION_MODE` | bool | false | No | Disable point verification mode. This mode performs IFFT on data before writing and FFT on data after reading. Disabling requires supplying the entire blob for verification against the KZG commitment. |
-| `--eigenda-max-blob-length` | `EIGENDA_PROXY_MAX_BLOB_LENGTH` | string | "2MiB" | No | Maximum blob length to be written or read from EigenDA. Determines the number of SRS points loaded into memory for KZG commitments. Example units: '30MiB', '4Kb', '30MB'. Maximum size slightly exceeds 1GB. |
-| `--eigenda-put-blob-encoding-version` | `EIGENDA_PROXY_PUT_BLOB_ENCODING_VERSION` | int | 0 | No | Blob encoding version to use when writing blobs from the high-level interface. |
-| `--eigenda-status-query-retry-interval` | `EIGENDA_PROXY_STATUS_QUERY_INTERVAL` | duration | 5s | No | Interval between retries when awaiting network blob finalization. |
-| `--eigenda-status-query-timeout` | `EIGENDA_PROXY_STATUS_QUERY_TIMEOUT` | duration | 30m0s | No | Duration to wait for a blob to finalize after being sent for dispersal. |
-| `--eigenda-response-timeout` | `EIGENDA_PROXY_RESPONSE_TIMEOUT` | duration | 10s | No | Total time to wait for a response from the EigenDA disperser. |
-| `--memstore.enabled` | `MEMSTORE_ENABLED` | bool | false | No | Whether to use mem-store for DA logic. |
-| `--memstore.expiration` | `MEMSTORE_EXPIRATION` | duration | 25m0s | No | Duration that a blob/commitment pair are allowed to live. |
-| `--metrics.addr` | `EIGENDA_PROXY_METRICS_ADDR` | string | "0.0.0.0" | No | Metrics listening address. |
-| `--metrics.enabled` | `EIGENDA_PROXY_METRICS_ENABLED` | bool | false | No | Enable the metrics server. |
-| `--metrics.port` | `EIGENDA_PROXY_METRICS_PORT` | int | 7300 | No | Metrics listening port. |
-| `--log.color` | `EIGENDA_PROXY_LOG_COLOR` | bool | false | No | Color the log output if in terminal mode. |
-| `--log.format` | `EIGENDA_PROXY_LOG_FORMAT` | string | text | No | Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'. |
-| `--log.level` | `EIGENDA_PROXY_LOG_LEVEL` | string | INFO | No | The lowest log level that will be output. |
-| `--log.pid` | `EIGENDA_PROXY_LOG_PID` | bool | false | No | Show pid in the log. |
+| CLI Flag Name | Env Var Flag Name | Input Type | Default Value | Required | Description |
+|----------------------------------------------|-------------------------------------------------|------------|---------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `--addr` | None | string | None | Yes | Server listening address. |
+| `--port` | None | int | None | Yes | Server listening port. |
+| `--eigenda-disperser-rpc` | `EIGENDA_PROXY_EIGENDA_DISPERSER_RPC` | string | None | Yes | RPC host of the EigenDA disperser service (e.g., on Holesky this is `disperser-holesky.eigenda.xyz:443`). Full network list available in the documentation. |
+| `--eigenda-signer-private-key-hex` | `EIGENDA_PROXY_SIGNER_PRIVATE_KEY_HEX` | string | None | Yes | Hex-encoded signer private key. This key should not be associated with an Ethereum address holding any funds. |
+| `--eigenda-eth-rpc` | `EIGENDA_PROXY_ETH_RPC` | string | None | Yes | JSON RPC node endpoint for the Ethereum network used for finalizing DA blobs. See available list here: |
+| `--eigenda-svc-manager-addr` | `EIGENDA_PROXY_SERVICE_MANAGER_ADDR` | string | None | Yes | The deployed EigenDA service manager address. The list can be found here: |
+| `--eigenda-g1-path` | `EIGENDA_PROXY_TARGET_KZG_G1_PATH` | string | "resources/g1.point" | No | Directory path to g1.point file. |
+| `--eigenda-g2-tau-path` | `EIGENDA_PROXY_TARGET_G2_TAU_PATH` | string | "resources/g2.point.powerOf2" | No | Directory path to g2.point.powerOf2 file. |
+| `--eigenda-cache-path` | `EIGENDA_PROXY_TARGET_CACHE_PATH` | string | "resources/SRSTables/" | No | Directory path to SRS tables for caching. |
+| `--eigenda-eth-confirmation-depth` | `EIGENDA_PROXY_ETH_CONFIRMATION_DEPTH` | int | 6 | No | 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-disable-tls` | `EIGENDA_PROXY_GRPC_DISABLE_TLS` | bool | false | No | Disable TLS for gRPC communication with the EigenDA disperser. |
+| `--eigenda-custom-quorum-ids` | `EIGENDA_PROXY_CUSTOM_QUORUM_IDS` | string | None | No | Custom quorum IDs for writing blobs. Should not include default quorums 0 or 1. |
+| `--eigenda-disable-point-verification-mode` | `EIGENDA_PROXY_DISABLE_POINT_VERIFICATION_MODE` | bool | false | No | Disable point verification mode. This mode performs IFFT on data before writing and FFT on data after reading. Disabling requires supplying the entire blob for verification against the KZG commitment. |
+| `--eigenda-max-blob-length` | `EIGENDA_PROXY_MAX_BLOB_LENGTH` | string | "2MiB" | No | Maximum blob length to be written or read from EigenDA. Determines the number of SRS points loaded into memory for KZG commitments. Example units: '30MiB', '4Kb', '30MB'. Maximum size slightly exceeds 1GB. |
+| `--eigenda-put-blob-encoding-version` | `EIGENDA_PROXY_PUT_BLOB_ENCODING_VERSION` | int | 0 | No | Blob encoding version to use when writing blobs from the high-level interface. |
+| `--eigenda-status-query-retry-interval` | `EIGENDA_PROXY_STATUS_QUERY_INTERVAL` | duration | 5s | No | Interval between retries when awaiting network blob finalization. |
+| `--eigenda-status-query-timeout` | `EIGENDA_PROXY_STATUS_QUERY_TIMEOUT` | duration | 30m0s | No | Duration to wait for a blob to finalize after being sent for dispersal. |
+| `--eigenda-response-timeout` | `EIGENDA_PROXY_RESPONSE_TIMEOUT` | duration | 10s | No | Total time to wait for a response from the EigenDA disperser. |
+| `--memstore.enabled` | `MEMSTORE_ENABLED` | bool | false | No | Whether to use mem-store for DA logic. |
+| `--memstore.expiration` | `MEMSTORE_EXPIRATION` | duration | 25m0s | No | Duration that a blob/commitment pair are allowed to live. |
+| `--metrics.addr` | `EIGENDA_PROXY_METRICS_ADDR` | string | "0.0.0.0" | No | Metrics listening address. |
+| `--metrics.enabled` | `EIGENDA_PROXY_METRICS_ENABLED` | bool | false | No | Enable the metrics server. |
+| `--metrics.port` | `EIGENDA_PROXY_METRICS_PORT` | int | 7300 | No | Metrics listening port. |
+| `--log.color` | `EIGENDA_PROXY_LOG_COLOR` | bool | false | No | Color the log output if in terminal mode. |
+| `--log.format` | `EIGENDA_PROXY_LOG_FORMAT` | string | text | No | Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty'. |
+| `--log.level` | `EIGENDA_PROXY_LOG_LEVEL` | string | INFO | No | The lowest log level that will be output. |
+| `--log.pid` | `EIGENDA_PROXY_LOG_PID` | bool | false | No | Show pid in the log. |
### Certificate verification
diff --git a/e2e/setup.go b/e2e/setup.go
index 8b3b4840..9370b2f4 100644
--- a/e2e/setup.go
+++ b/e2e/setup.go
@@ -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(
diff --git a/go.mod b/go.mod
index fcd254ca..c9a356ca 100644
--- a/go.mod
+++ b/go.mod
@@ -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.4-0.20240626205405-753d42726d25
github.com/consensys/gnark-crypto v0.12.1
github.com/ethereum-optimism/optimism v1.7.7
github.com/ethereum/go-ethereum v1.14.0
diff --git a/go.sum b/go.sum
index d72d90ed..386587a0 100644
--- a/go.sum
+++ b/go.sum
@@ -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.4-0.20240626205405-753d42726d25 h1:r5+qUMV/ZMPRo54+4w6S5pO9HLYCQUrrA7wwSRkQlpQ=
+github.com/Layr-Labs/eigenda v0.7.4-0.20240626205405-753d42726d25/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=
diff --git a/server/config.go b/server/config.go
index aebfc209..2f1b109b 100644
--- a/server/config.go
+++ b/server/config.go
@@ -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"
@@ -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
@@ -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,
}
}
@@ -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
}
@@ -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.",
diff --git a/server/eigenda_store.go b/server/eigenda_store.go
index 6b5066e4..f4a2a044 100644
--- a/server/eigenda_store.go
+++ b/server/eigenda_store.go
@@ -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
}
@@ -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
@@ -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 {
@@ -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)
diff --git a/server/load_store.go b/server/load_store.go
index 41458c23..327cc3c4 100644
--- a/server/load_store.go
+++ b/server/load_store.go
@@ -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,
+ },
)
}
diff --git a/verify/cert.go b/verify/cert.go
index 6f55b430..48d5f2e2 100644
--- a/verify/cert.go
+++ b/verify/cert.go
@@ -1,9 +1,14 @@
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"
@@ -11,10 +16,15 @@ import (
"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) {
@@ -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 {
@@ -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
+}
diff --git a/verify/finalized_block_number_client.go b/verify/finalized_block_number_client.go
new file mode 100644
index 00000000..46ca3290
--- /dev/null
+++ b/verify/finalized_block_number_client.go
@@ -0,0 +1,169 @@
+package verify
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "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
+}
+
+type rpcBlock struct {
+ Hash common.Hash `json:"hash"`
+ Transactions []rpcTransaction `json:"transactions"`
+ UncleHashes []common.Hash `json:"uncles"`
+ Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
+}
+type rpcTransaction struct {
+ tx *types.Transaction
+ txExtraInfo
+}
+type txExtraInfo struct {
+ BlockNumber *string `json:"blockNumber,omitempty"`
+ BlockHash *common.Hash `json:"blockHash,omitempty"`
+ From *common.Address `json:"from,omitempty"`
+}
+
+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
+ }
+
+ // var body rpcBlock
+ // if err := json.Unmarshal(raw, &body); err != nil {
+ // return nil, err
+ // }
+ // // Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
+ // if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
+ // return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles")
+ // }
+ // if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
+ // return nil, errors.New("server returned empty uncle list but block header indicates uncles")
+ // }
+ // if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 {
+ // return nil, errors.New("server returned non-empty transaction list but block header indicates no transactions")
+ // }
+ // if head.TxHash != types.EmptyTxsHash && len(body.Transactions) == 0 {
+ // return nil, errors.New("server returned empty transaction list but block header indicates transactions")
+ // }
+ // // Load uncles because they are not included in the block response.
+ // var uncles []*types.Header
+ // if len(body.UncleHashes) > 0 {
+ // uncles = make([]*types.Header, len(body.UncleHashes))
+ // reqs := make([]rpc.BatchElem, len(body.UncleHashes))
+ // for i := range reqs {
+ // reqs[i] = rpc.BatchElem{
+ // Method: "eth_getUncleByBlockHashAndIndex",
+ // Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))},
+ // Result: &uncles[i],
+ // }
+ // }
+ // if err := c.Client().BatchCallContext(ctx, reqs); err != nil {
+ // return nil, err
+ // }
+ // for i := range reqs {
+ // if reqs[i].Error != nil {
+ // return nil, reqs[i].Error
+ // }
+ // if uncles[i] == nil {
+ // return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:])
+ // }
+ // }
+ // }
+ // // Fill the sender cache of transactions in the block.
+ // txs := make([]*types.Transaction, len(body.Transactions))
+ // for i, tx := range body.Transactions {
+ // if tx.From != nil {
+ // setSenderFromServer(tx.tx, *tx.From, body.Hash)
+ // }
+ // txs[i] = tx.tx
+ // }
+ return types.NewBlockWithHeader(head), nil
+ // .WithBody(txs, uncles).WithWithdrawals(body.Withdrawals), nil
+}
+
+// senderFromServer is a types.Signer that remembers the sender address returned by the RPC
+// server. It is stored in the transaction's sender address cache to avoid an additional
+// request in TransactionSender.
+type senderFromServer struct {
+ addr common.Address
+ blockhash common.Hash
+}
+
+func (s *senderFromServer) Equal(other types.Signer) bool {
+ os, ok := other.(*senderFromServer)
+ return ok && os.blockhash == s.blockhash
+}
+
+var errNotCached = errors.New("sender not cached")
+
+func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) {
+ if s.addr == (common.Address{}) {
+ return common.Address{}, errNotCached
+ }
+ return s.addr, nil
+}
+
+func (s *senderFromServer) ChainID() *big.Int {
+ panic("can't sign with senderFromServer")
+}
+func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash {
+ panic("can't sign with senderFromServer")
+}
+func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) {
+ panic("can't sign with senderFromServer")
+}
+
+func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) {
+ // Use types.Sender for side-effect to store our signer into the cache.
+ types.Sender(&senderFromServer{addr, block}, tx)
+}
diff --git a/verify/verifier.go b/verify/verifier.go
index 8b69d0b7..a4dd28f8 100644
--- a/verify/verifier.go
+++ b/verify/verifier.go
@@ -17,10 +17,11 @@ import (
)
type Config struct {
- Verify bool
- RPCURL string
- SvcManagerAddr string
- KzgConfig *kzg.KzgConfig
+ Verify bool
+ RPCURL string
+ SvcManagerAddr string
+ KzgConfig *kzg.KzgConfig
+ EthConfirmationDepth uint64
}
type Verifier struct {