Skip to content

Commit

Permalink
Additional correctness tests (#1208)
Browse files Browse the repository at this point in the history
Signed-off-by: Cody Littley <[email protected]>
  • Loading branch information
cody-littley authored Feb 5, 2025
1 parent 0fdef92 commit 3879e4f
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 165 deletions.
7 changes: 7 additions & 0 deletions common/testutils/random/test_random.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ func (r *TestRandom) String(length int) string {
return string(b)
}

// VariableString generates a random string out of printable ASCII characters of a length between
// min (inclusive) and max (exclusive).
func (r *TestRandom) VariableString(min int, max int) string {
length := r.Intn(max-min) + min
return r.String(length)
}

// Uint32n generates a random uint32 less than n.
func (r *TestRandom) Uint32n(n uint32) uint32 {
return r.Uint32() % n
Expand Down
65 changes: 36 additions & 29 deletions test/v2/test_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ const (

// TestClient encapsulates the various clients necessary for interacting with EigenDA.
type TestClient struct {
t *testing.T
config *TestClientConfig
logger logging.Logger
T *testing.T
Config *TestClientConfig
Logger logging.Logger
DisperserClient clients.DisperserClient
RelayClient clients.RelayClient
indexedChainState core.IndexedChainState
RetrievalClient clients.RetrievalClient
CertVerifier *verification.CertVerifier
PrivateKey string
}

type TestClientConfig struct {
Expand Down Expand Up @@ -83,6 +84,11 @@ func (c *TestClientConfig) path(t *testing.T, elements ...string) string {
// NewTestClient creates a new TestClient instance.
func NewTestClient(t *testing.T, config *TestClientConfig) *TestClient {

if config.SRSNumberToLoad == 0 {
// See https://github.com/Layr-Labs/eigenda/pull/1208#discussion_r1941571297
config.SRSNumberToLoad = config.MaxBlobSize / 32 / 4096 * 8
}

var loggerConfig common.LoggerConfig
if os.Getenv("CI") != "" {
loggerConfig = common.DefaultLoggerConfig()
Expand Down Expand Up @@ -204,14 +210,15 @@ func NewTestClient(t *testing.T, config *TestClientConfig) *TestClient {
require.NoError(t, err)

return &TestClient{
t: t,
config: config,
logger: logger,
T: t,
Config: config,
Logger: logger,
DisperserClient: disperserClient,
RelayClient: relayClient,
indexedChainState: indexedChainState,
RetrievalClient: retrievalClient,
CertVerifier: certVerifier,
PrivateKey: privateKeyString,
}
}

Expand Down Expand Up @@ -270,7 +277,7 @@ func (c *TestClient) WaitForCertification(
select {
case <-ticker.C:
reply, err := c.DisperserClient.GetBlobStatus(ctx, key)
require.NoError(c.t, err)
require.NoError(c.T, err)

if reply.Status == v2.BlobStatus_COMPLETE {
elapsed := time.Since(statusStart)
Expand Down Expand Up @@ -303,13 +310,13 @@ func (c *TestClient) WaitForCertification(
if reply.Status == v2.BlobStatus_FAILED ||
reply.Status == v2.BlobStatus_UNKNOWN {
require.Fail(
c.t,
c.T,
"Blob status is in a terminal non-successful state.",
reply.Status.String())
}
}
case <-ctx.Done():
require.Fail(c.t, "Timed out waiting for blob to be confirmed")
require.Fail(c.T, "Timed out waiting for blob to be confirmed")
}
}
}
Expand All @@ -322,25 +329,25 @@ func (c *TestClient) VerifyBlobCertification(
inclusionInfo *v2.BlobInclusionInfo) {

blobCert := inclusionInfo.BlobCertificate
require.NotNil(c.t, blobCert)
require.True(c.t, len(blobCert.RelayKeys) >= 1)
require.NotNil(c.T, blobCert)
require.True(c.T, len(blobCert.RelayKeys) >= 1)

// make sure the returned header hash matches the expected blob key
bh, err := corev2.BlobHeaderFromProtobuf(blobCert.BlobHeader)
require.NoError(c.t, err)
require.NoError(c.T, err)
computedBlobKey, err := bh.BlobKey()
require.NoError(c.t, err)
require.Equal(c.t, key, computedBlobKey)
require.NoError(c.T, err)
require.Equal(c.T, key, computedBlobKey)

// verify that expected quorums are present
quorumSet := make(map[core.QuorumID]struct{}, len(expectedQuorums))
for _, quorumNumber := range signedBatch.Attestation.QuorumNumbers {
quorumSet[core.QuorumID(quorumNumber)] = struct{}{}
}
// There may be other quorums in the batch. No biggie as long as the expected ones are there.
require.True(c.t, len(expectedQuorums) <= len(quorumSet))
require.True(c.T, len(expectedQuorums) <= len(quorumSet))
for expectedQuorum := range quorumSet {
require.Contains(c.t, quorumSet, expectedQuorum)
require.Contains(c.T, quorumSet, expectedQuorum)
}

// Check the signing percentages
Expand All @@ -351,15 +358,15 @@ func (c *TestClient) VerifyBlobCertification(
}
for _, quorum := range expectedQuorums {
percent, ok := signingPercents[quorum]
require.True(c.t, ok)
require.True(c.t, percent >= 0 && percent <= 100)
require.True(c.t, percent >= c.config.MinimumSigningPercent)
require.True(c.T, ok)
require.True(c.T, percent >= 0 && percent <= 100)
require.True(c.T, percent >= c.Config.MinimumSigningPercent)
}

// TODO This currently does not pass!
// On-chain verification
// err = c.CertVerifier.VerifyCertV2FromSignedBatch(context.Background(), signedBatch, inclusionInfo)
// require.NoError(c.t, err)
//err = c.CertVerifier.VerifyCertV2FromSignedBatch(context.Background(), signedBatch, inclusionInfo)
//require.NoError(c.T, err)
}

// ReadBlobFromRelay reads a blob from the relays and compares it to the given payload.
Expand All @@ -372,10 +379,10 @@ func (c *TestClient) ReadBlobFromRelay(
for _, relayID := range blobCert.RelayKeys {
fmt.Printf("Reading blob from relay %d\n", relayID)
blobFromRelay, err := c.RelayClient.GetBlob(ctx, relayID, key)
require.NoError(c.t, err)
require.NoError(c.T, err)

relayPayload := codec.RemoveEmptyByteFromPaddedBytes(blobFromRelay)
require.Equal(c.t, payload, relayPayload)
require.Equal(c.T, payload, relayPayload)
}
}

Expand All @@ -387,27 +394,27 @@ func (c *TestClient) ReadBlobFromValidators(
payload []byte) {

currentBlockNumber, err := c.indexedChainState.GetCurrentBlockNumber()
require.NoError(c.t, err)
require.NoError(c.T, err)

for _, quorumID := range quorums {
fmt.Printf("Reading blob from validators for quorum %d\n", quorumID)
header, err := corev2.BlobHeaderFromProtobuf(blobCert.BlobHeader)
require.NoError(c.t, err)
require.NoError(c.T, err)

retrievedBlob, err := c.RetrievalClient.GetBlob(ctx, header, uint64(currentBlockNumber), quorumID)
require.NoError(c.t, err)
require.NoError(c.T, err)

retrievedPayload := codec.RemoveEmptyByteFromPaddedBytes(retrievedBlob)

// The payload may have a bunch of 0s appended at the end. Remove them.
require.True(c.t, len(retrievedPayload) >= len(payload))
require.True(c.T, len(retrievedPayload) >= len(payload))
truncatedPayload := retrievedPayload[:len(payload)]

// Only 0s should be appended at the end.
for i := len(payload); i < len(retrievedPayload); i++ {
require.Equal(c.t, byte(0), retrievedPayload[i])
require.Equal(c.T, byte(0), retrievedPayload[i])
}

require.Equal(c.t, payload, truncatedPayload)
require.Equal(c.T, payload, truncatedPayload)
}
}
130 changes: 130 additions & 0 deletions test/v2/test_setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package v2

import (
"fmt"
"github.com/docker/go-units"
"github.com/stretchr/testify/require"
"os"
"os/exec"
"sync"
"testing"
)

var (
preprodConfig = &TestClientConfig{
TestDataPath: "~/.test-v2",
DisperserHostname: "disperser-preprod-holesky.eigenda.xyz",
DisperserPort: 443,
EthRPCURLs: []string{"https://ethereum-holesky-rpc.publicnode.com"},
BLSOperatorStateRetrieverAddr: "0x93545e3b9013CcaBc31E80898fef7569a4024C0C",
EigenDAServiceManagerAddr: "0x54A03db2784E3D0aCC08344D05385d0b62d4F432",
EigenDACertVerifierAddress: "0xe2C7AfB3c47B800b439b0a3d8EA40ca79759B245",
SubgraphURL: "https://subgraph.satsuma-prod.com/51caed8fa9cb/eigenlabs/eigenda-operator-state-preprod-holesky/version/v0.7.0/api",
SRSOrder: 268435456,
MaxBlobSize: 16 * units.MiB,
MinimumSigningPercent: 55,
}

lock sync.Mutex
client *TestClient

targetConfig = preprodConfig
)

// getClient returns a TestClient instance, creating one if it does not exist.
// This uses a global static client... this is icky, but it takes ~1 minute
// to read the SRS points, so it's the lesser of two evils to keep it around.
func getClient(t *testing.T) *TestClient {
lock.Lock()
defer lock.Unlock()

skipInCI(t)
setupFilesystem(t, targetConfig)

if client == nil {
client = NewTestClient(t, targetConfig)
}

return client
}

func skipInCI(t *testing.T) {
if os.Getenv("CI") != "" {
t.Skip("Skipping test in CI environment")
}
}

func setupFilesystem(t *testing.T, config *TestClientConfig) {
// Create the test data directory if it does not exist
err := os.MkdirAll(config.TestDataPath, 0755)
require.NoError(t, err)

// Create the SRS directories if they do not exist
err = os.MkdirAll(config.path(t, SRSPath), 0755)
require.NoError(t, err)
err = os.MkdirAll(config.path(t, SRSPathSRSTables), 0755)
require.NoError(t, err)

// If any of the srs files do not exist, download them.
filePath := config.path(t, SRSPathG1)
_, err = os.Stat(filePath)
if os.IsNotExist(err) {
command := make([]string, 3)
command[0] = "wget"
command[1] = "https://srs-mainnet.s3.amazonaws.com/kzg/g1.point"
command[2] = "--output-document=" + filePath
fmt.Printf("executing %s\n", command)

cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
} else {
require.NoError(t, err)
}

filePath = config.path(t, SRSPathG2)
_, err = os.Stat(filePath)
if os.IsNotExist(err) {
command := make([]string, 3)
command[0] = "wget"
command[1] = "https://srs-mainnet.s3.amazonaws.com/kzg/g2.point"
command[2] = "--output-document=" + filePath
fmt.Printf("executing %s\n", command)

cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
} else {
require.NoError(t, err)
}

filePath = config.path(t, SRSPathG2PowerOf2)
_, err = os.Stat(filePath)
if os.IsNotExist(err) {
command := make([]string, 3)
command[0] = "wget"
command[1] = "https://srs-mainnet.s3.amazonaws.com/kzg/g2.point.powerOf2"
command[2] = "--output-document=" + filePath
fmt.Printf("executing %s\n", command)

cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
} else {
require.NoError(t, err)
}

// Check to see if the private key file exists. If not, stop the test.
filePath = config.path(t, KeyPath)
_, err = os.Stat(filePath)
require.NoError(t, err,
"private key file %s does not exist. This file should "+
"contain the private key for the account used in the test, in hex.",
filePath)
}
Loading

0 comments on commit 3879e4f

Please sign in to comment.