Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding fuzz test and workflow #1036

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/integration-fuzz-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: integration-tests
on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
integration-tests:
name: Fuzz Tests
runs-on: ubuntu-latest
steps:
- name: Add LocalStack AWS Credentials
run: |
mkdir -p ~/.aws
touch ~/.aws/credentials

echo '[default]' >> ~/.aws/credentials
echo 'aws_access_key_id=localstack' >> ~/.aws/credentials
echo 'aws_secret_access_key=localstack' >> ~/.aws/credentials

- name: Set Test Profile to default
run: |
aws configure --profile test-profile set region us-east-1
aws configure --profile test-profile set source_profile default

- uses: actions/setup-go@v3
with:
go-version: "1.21" # The Go version to download (if necessary) and use.
- run: go version

- name: Checkout EigenDA
uses: actions/checkout@v3

- name: Update Submodule Commits
run: |
git submodule update --init --recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-293fad73670b7b59ca901c7f2105bf7a29165a90

- name: Install graph cli
run: |
yarn global add @graphprotocol/graph-cli@0.51.0

- name: Get Forge version
run: forge --version

- name: Build contracts
run: |
cd contracts && forge build

- name: Inabox E2E
run: make build && cd inabox && make run-e2e-fuzz

- name: Save logs
if: always()
uses: actions/upload-artifact@v3
with:
name: inabox-logs
path: |
inabox/testdata/*/logs/
inabox/testdata/*/deploy.log

3 changes: 3 additions & 0 deletions inabox/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ stop-infra:
./bin.sh stop-graph
docker stop localstack-test

run-e2e-fuzz:
go test --fuzz -fuzztime=1m ./tests -v -config=../templates/testconfig-anvil.yaml

run-e2e:
go test ./tests -v -config=../templates/testconfig-anvil.yaml

Expand Down
248 changes: 248 additions & 0 deletions inabox/tests/integration_v2_fuzz_send_receive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package integration_test

import (
"bytes"
"context"
"crypto/rand"
"fmt"
"testing"
"time"

"github.com/Layr-Labs/eigenda/api/clients/v2"
commonpb "github.com/Layr-Labs/eigenda/api/grpc/common/v2"
disperserpb "github.com/Layr-Labs/eigenda/api/grpc/disperser/v2"
"github.com/Layr-Labs/eigenda/core"
auth "github.com/Layr-Labs/eigenda/core/auth/v2"
corev2 "github.com/Layr-Labs/eigenda/core/v2"
dispv2 "github.com/Layr-Labs/eigenda/disperser/common/v2"
"github.com/Layr-Labs/eigenda/encoding/utils/codec"
"github.com/wealdtech/go-merkletree/v2"
"github.com/wealdtech/go-merkletree/v2/keccak256"
)

// FuzzInaboxIntegration tests the Inabox v2 integration with randomized inputs.
func FuzzInaboxIntegration(f *testing.F) {
// Define seed inputs with edge cases for data1
seedInputs := [][]byte{
{}, // 0 bytes
make([]byte, 1), // Minimal non-empty input
bytes.Repeat([]byte{0x00}, 100), // All zeros
bytes.Repeat([]byte{0xFF}, 100), // All 0xFF bytes
[]byte("Hello, 世界"), // Unicode characters
make([]byte, 1024), // 1KB of zeros
func() []byte { // Random 4MB
data := make([]byte, 4*1024*1024) // 4MB
_, err := rand.Read(data)
if err != nil {
panic(fmt.Sprintf("Failed to generate random seed data: %v", err))
}
return data
}(),
}

// Add seed inputs to the fuzzer
for _, seed := range seedInputs {
f.Add(seed)
}

// Define the fuzzing function
f.Fuzz(func(t *testing.T, data1 []byte) {
// Limit data1 to a maximum of 4MB
const maxSize = 4 * 1024 * 1024 // 4MB
if len(data1) > maxSize {
data1 = data1[:maxSize]
}

ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

// Initialize the signer with the provided private key
privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded"
signer := auth.NewLocalBlobRequestSigner(privateKeyHex)

// Create the Disperser client
disp, err := clients.NewDisperserClient(&clients.DisperserClientConfig{
Hostname: "localhost",
Port: "32005",
}, signer, nil, nil)
if err != nil {
t.Errorf("Failed to create Disperser client: %v", err)
return
}
if disp == nil {
t.Errorf("Disperser client is nil")
return
}

// Pad data1 as required by the codec
paddedData1 := codec.ConvertByPaddingEmptyByte(data1)

// Disperse the blob
blobStatus, key, err := disp.DisperseBlob(ctx, paddedData1, 0, []uint8{0, 1}, 0)
if err != nil {
t.Errorf("DisperseBlob for data1 failed: %v", err)
return
}
if key.Hex() == "" {
t.Errorf("DisperseBlob for data1 returned nil key")
return
}
if blobStatus == nil {
t.Errorf("DisperseBlob for data1 returned nil status")
return
}
if *blobStatus != dispv2.Queued {
t.Errorf("Expected blobStatus to be Queued, got %v", *blobStatus)
return
}

// Poll for certification
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()

var reply *disperserpb.BlobStatusReply
var blobCert *corev2.BlobCertificate
var batchHeader *commonpb.BatchHeader

for loop := true; loop; {
select {
case <-ctx.Done():
t.Error("Test timed out while waiting for blob certification")
return
case <-ticker.C:
reply, err = disp.GetBlobStatus(context.Background(), key)
if err != nil {
t.Errorf("GetBlobStatus failed: %v", err)
return
}
if reply == nil {
t.Error("GetBlobStatus returned nil reply")
return
}
status, err := dispv2.BlobStatusFromProtobuf(reply.GetStatus())
if err != nil {
t.Errorf("BlobStatusFromProtobuf failed: %v", err)
return
}

if status != dispv2.Certified {
// Blob not yet certified; continue polling
continue
}

// Process the certified blob
batchHeader = reply.GetSignedBatch().GetHeader()
if batchHeader == nil {
t.Error("batchHeader is nil")
return
}
if batchHeader.GetBatchRoot() == nil {
t.Error("batchHeader.BatchRoot is nil")
return
}
if batchHeader.GetReferenceBlockNumber() <= 0 {
t.Errorf("batchHeader.ReferenceBlockNumber expected > 0, got %d", batchHeader.GetReferenceBlockNumber())
return
}

attestation := reply.GetSignedBatch().GetAttestation()
if attestation == nil {
t.Error("attestation is nil")
return
}
if !bytes.Equal(attestation.QuorumSignedPercentages, []byte{100, 100}) {
t.Errorf("Expected quorumSignedPercentages to be [100, 100], got %v", attestation.QuorumSignedPercentages)
return
}

blobVerification := reply.GetBlobVerificationInfo()
if blobVerification == nil {
t.Error("blobVerification is nil")
return
}
if blobVerification.GetBlobCertificate() == nil {
t.Error("blobVerification.BlobCertificate is nil")
return
}

blobCert, err = corev2.BlobCertificateFromProtobuf(blobVerification.GetBlobCertificate())
if err != nil {
t.Errorf("BlobCertificateFromProtobuf failed: %v", err)
return
}

inclusionProofBytes := blobVerification.GetInclusionProof()
blobIndex := blobVerification.GetBlobIndex()
proof, err := core.DeserializeMerkleProof(inclusionProofBytes, uint64(blobIndex))
if err != nil {
t.Errorf("DeserializeMerkleProof failed: %v", err)
return
}

certHash, err := blobCert.Hash()
if err != nil {
t.Errorf("BlobCertificate Hash failed: %v", err)
return
}

verified, err := merkletree.VerifyProofUsing(certHash[:], false, proof, [][]byte{batchHeader.BatchRoot}, keccak256.New())
if err != nil {
t.Errorf("VerifyProofUsing failed: %v", err)
return
}
if !verified {
t.Error("Merkle proof verification failed")
return
}

// Blob is certified and verified; exit the loop
loop = false
}
}

// Test retrieval from relay
relayClient, err := clients.NewRelayClient(&clients.RelayClientConfig{
Sockets: relays,
}, logger)
if err != nil {
t.Errorf("Failed to create Relay client: %v", err)
return
}

blobRelays := make(map[corev2.RelayKey]struct{}, len(blobCert.RelayKeys))
for _, k := range blobCert.RelayKeys {
blobRelays[corev2.RelayKey(k)] = struct{}{}
}

for relayKey := range relays { // Ensure 'relays' is defined
blob, err := relayClient.GetBlob(ctx, relayKey, key)
if _, ok := blobRelays[corev2.RelayKey(relayKey)]; ok {
if err != nil {
t.Errorf("GetBlob from relay %v failed: %v", relayKey, err)
continue
}
if !bytes.Equal(blob, paddedData1) {
t.Errorf("Retrieved blob data mismatch from relay %v", relayKey)
}
} else {
if err == nil {
t.Errorf("Expected error when retrieving blob from non-associated relay %v", relayKey)
}
}
}

// Test retrieval from retrievalClientV2
// Ensure 'retrievalClientV2' is defined or initialized appropriately
b, err := retrievalClientV2.GetBlob(ctx, blobCert.BlobHeader, batchHeader.ReferenceBlockNumber, 0)
if err != nil {
t.Errorf("retrievalClientV2.GetBlob failed: %v", err)
} else {
restored := bytes.TrimRight(b, "\x00")
if !bytes.Equal(restored, paddedData1) {
t.Errorf("Restored blob data does not match original data")
}
}

// additional retrieval checks if necessary
})
}
Loading