From c98d08011a1c942ec2a992232fdb9183083bcd50 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Thu, 24 Oct 2024 15:48:16 -0700 Subject: [PATCH 01/17] feat: disperser server payment metering API --- core/meterer/meterer.go | 1 - core/meterer/onchain_state.go | 1 + disperser/apiserver/server.go | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/meterer/meterer.go b/core/meterer/meterer.go index 53ac0848af..e29cc7c1bb 100644 --- a/core/meterer/meterer.go +++ b/core/meterer/meterer.go @@ -252,7 +252,6 @@ func (m *Meterer) SymbolsCharged(dataLength uint) uint32 { return m.ChainPaymentState.GetMinNumSymbols() } // Round up to the nearest multiple of MinNumSymbols - fmt.Println("return ", uint32(core.RoundUpDivide(uint(dataLength), uint(m.ChainPaymentState.GetMinNumSymbols())))*m.ChainPaymentState.GetMinNumSymbols()) return uint32(core.RoundUpDivide(uint(dataLength), uint(m.ChainPaymentState.GetMinNumSymbols()))) * m.ChainPaymentState.GetMinNumSymbols() } diff --git a/core/meterer/onchain_state.go b/core/meterer/onchain_state.go index 7db5a1bd55..2725ef765e 100644 --- a/core/meterer/onchain_state.go +++ b/core/meterer/onchain_state.go @@ -105,6 +105,7 @@ func (pcs *OnchainPaymentState) RefreshOnchainPaymentState(ctx context.Context, if err != nil { return err } + // These parameters should be rarely updated, but we refresh them anyway pcs.PaymentVaultParams = paymentVaultParams return nil } diff --git a/disperser/apiserver/server.go b/disperser/apiserver/server.go index cf9b1e8538..e499920c78 100644 --- a/disperser/apiserver/server.go +++ b/disperser/apiserver/server.go @@ -260,7 +260,7 @@ func (s *DispersalServer) DisperseBlob(ctx context.Context, req *pb.DisperseBlob // to track the error again. func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, authenticatedAddress string, apiMethodName string, paymentHeader *core.PaymentMetadata) (*pb.DisperseBlobReply, error) { timer := prometheus.NewTimer(prometheus.ObserverFunc(func(f float64) { - s.metrics.ObserveLatency("DisperseBlob", f*1000) // make milliseconds + s.metrics.ObserveLatency(apiMethodName, f*1000) // make milliseconds })) defer timer.ObserveDuration() From cb1f9d16f2bf5d2ca25b0408b4596d5e252dcaa2 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Fri, 25 Oct 2024 10:03:55 -0700 Subject: [PATCH 02/17] chore: test fix and startup query --- core/meterer/meterer.go | 5 +++++ disperser/apiserver/server_test.go | 1 + 2 files changed, 6 insertions(+) diff --git a/core/meterer/meterer.go b/core/meterer/meterer.go index e29cc7c1bb..f208d5e476 100644 --- a/core/meterer/meterer.go +++ b/core/meterer/meterer.go @@ -55,6 +55,11 @@ func (m *Meterer) Start(ctx context.Context) { ticker := time.NewTicker(m.UpdateInterval) defer ticker.Stop() + // initial tick immediately upto Start + if err := m.ChainPaymentState.RefreshOnchainPaymentState(ctx, nil); err != nil { + m.logger.Error("Failed to make initial query to the on-chain state", "error", err) + } + for { select { case <-ticker.C: diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index 4f6535769a..3e0750701e 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -214,6 +214,7 @@ func TestDisperseBlobWithInvalidQuorum(t *testing.T) { } ctx := peer.NewContext(context.Background(), p) + // (*dispersalServer).tx.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{0, 1}, nil).Twice() _, err = dispersalServer.DisperseBlob(ctx, &pb.DisperseBlobRequest{ Data: data, CustomQuorumNumbers: []uint32{2}, From 94cd6c26fdf3d6f2217d4bd1f5ae1f6da0058eea Mon Sep 17 00:00:00 2001 From: hopeyen Date: Fri, 25 Oct 2024 11:37:35 -0700 Subject: [PATCH 03/17] refactor: server payment state init --- core/meterer/meterer.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/meterer/meterer.go b/core/meterer/meterer.go index f208d5e476..e29cc7c1bb 100644 --- a/core/meterer/meterer.go +++ b/core/meterer/meterer.go @@ -55,11 +55,6 @@ func (m *Meterer) Start(ctx context.Context) { ticker := time.NewTicker(m.UpdateInterval) defer ticker.Stop() - // initial tick immediately upto Start - if err := m.ChainPaymentState.RefreshOnchainPaymentState(ctx, nil); err != nil { - m.logger.Error("Failed to make initial query to the on-chain state", "error", err) - } - for { select { case <-ticker.C: From 9779cffaea90d844c87c5ef903e46bb71bac3e4e Mon Sep 17 00:00:00 2001 From: hopeyen Date: Fri, 25 Oct 2024 12:57:41 -0700 Subject: [PATCH 04/17] refactor: add and rm comments --- disperser/apiserver/server_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index 3e0750701e..4f6535769a 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -214,7 +214,6 @@ func TestDisperseBlobWithInvalidQuorum(t *testing.T) { } ctx := peer.NewContext(context.Background(), p) - // (*dispersalServer).tx.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{0, 1}, nil).Twice() _, err = dispersalServer.DisperseBlob(ctx, &pb.DisperseBlobRequest{ Data: data, CustomQuorumNumbers: []uint32{2}, From a8e78a1d6d59a3d460299a0197eefdd95ad79157 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Mon, 28 Oct 2024 18:24:34 -0700 Subject: [PATCH 05/17] feat: client payment api integration --- api/clients/accountant.go | 154 ++++++++++ api/clients/accountant_test.go | 439 +++++++++++++++++++++++++++ api/clients/disperser_client.go | 52 +++- api/clients/eigenda_client.go | 132 +++++++- api/clients/mock/disperser_client.go | 20 +- api/clients/retrieval_client.go | 1 + core/data.go | 9 + core/meterer/meterer.go | 7 +- core/meterer/meterer_test.go | 96 +++--- disperser/apiserver/payment_test.go | 150 +++++++++ disperser/apiserver/server.go | 3 +- disperser/apiserver/server_test.go | 23 +- inabox/deploy/config.go | 24 +- inabox/deploy/env_vars.go | 8 + inabox/tests/integration_test.go | 9 +- inabox/tests/payment_test.go | 155 ++++++++++ inabox/tests/ratelimit_test.go | 2 +- test/integration_test.go | 16 +- 18 files changed, 1207 insertions(+), 93 deletions(-) create mode 100644 api/clients/accountant.go create mode 100644 api/clients/accountant_test.go create mode 100644 disperser/apiserver/payment_test.go create mode 100644 inabox/tests/payment_test.go diff --git a/api/clients/accountant.go b/api/clients/accountant.go new file mode 100644 index 0000000000..cd3484bd39 --- /dev/null +++ b/api/clients/accountant.go @@ -0,0 +1,154 @@ +package clients + +import ( + "context" + "fmt" + "math/big" + "sync" + "time" + + commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/core" + "github.com/Layr-Labs/eigenda/core/meterer" +) + +type IAccountant interface { + AccountBlob(ctx context.Context, data []byte, quorums []uint8) (uint32, uint64, error) +} + +type Accountant struct { + // on-chain states + reservation core.ActiveReservation + onDemand core.OnDemandPayment + reservationWindow uint32 + pricePerSymbol uint32 + minNumSymbols uint32 + + // local accounting + // contains 3 bins; index 0 for current bin, 1 for next bin, 2 for overflowed bin + binUsages []uint64 + usageLock sync.Mutex + cumulativePayment *big.Int + stopRotation chan struct{} + + paymentSigner core.PaymentSigner +} + +func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPayment, reservationWindow uint32, pricePerSymbol uint32, minNumSymbols uint32, paymentSigner core.PaymentSigner) *Accountant { + //TODO: client storage; currently every instance starts fresh but on-chain or a small store makes more sense + // Also client is currently responsible for supplying network params, we need to add RPC in order to be automatic + // There's a subsequent PR that handles populating the accountant with on-chain state from the disperser + a := Accountant{ + reservation: reservation, + onDemand: onDemand, + reservationWindow: reservationWindow, + pricePerSymbol: pricePerSymbol, + minNumSymbols: minNumSymbols, + binUsages: []uint64{0, 0, 0}, + cumulativePayment: big.NewInt(0), + stopRotation: make(chan struct{}), + paymentSigner: paymentSigner, + } + go a.startBinRotation() + // TODO: add a routine to refresh the on-chain state occasionally? + return &a +} + +func (a *Accountant) startBinRotation() { + ticker := time.NewTicker(time.Duration(a.reservationWindow) * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + a.rotateBins() + case <-a.stopRotation: + return + } + } +} + +func (a *Accountant) rotateBins() { + a.usageLock.Lock() + defer a.usageLock.Unlock() + // Shift bins: bin_i to bin_{i-1}, set 0 to bin2 + a.binUsages[0] = a.binUsages[1] + a.binUsages[1] = a.binUsages[2] + a.binUsages[2] = 0 +} + +func (a *Accountant) Stop() { + close(a.stopRotation) +} + +// accountant calculates and records payment information +func (a *Accountant) BlobPaymentInfo(ctx context.Context, dataLength uint64) (uint32, *big.Int, error) { + //TODO: do we need to lock the binUsages here in case the blob rotation happens in the middle of the function? + // binUsage := a.binUsages[0] + dataLength + a.usageLock.Lock() + defer a.usageLock.Unlock() + a.binUsages[0] += dataLength + now := time.Now().Unix() + currentBinIndex := meterer.GetBinIndex(uint64(now), a.reservationWindow) + + // first attempt to use the active reservation + binLimit := a.reservation.SymbolsPerSec * uint64(a.reservationWindow) + if a.binUsages[0] <= binLimit { + return currentBinIndex, big.NewInt(0), nil + } + + // Allow one overflow when the overflow bin is empty, the current usage and new length are both less than the limit + if a.binUsages[2] == 0 && a.binUsages[0]-dataLength < binLimit && dataLength <= binLimit { + a.binUsages[2] += a.binUsages[0] - binLimit + return currentBinIndex, big.NewInt(0), nil + } + + // reservation not available, attempt on-demand + //todo: rollback if disperser respond with some type of rejection? + a.binUsages[0] -= dataLength + incrementRequired := big.NewInt(int64(a.PaymentCharged(uint(dataLength)))) + a.cumulativePayment.Add(a.cumulativePayment, incrementRequired) + if a.cumulativePayment.Cmp(a.onDemand.CumulativePayment) <= 0 { + return 0, a.cumulativePayment, nil + } + return 0, big.NewInt(0), fmt.Errorf("Accountant cannot approve payment for this blob") +} + +// accountant provides and records payment information +func (a *Accountant) AccountBlob(ctx context.Context, dataLength uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) { + binIndex, cumulativePayment, err := a.BlobPaymentInfo(ctx, dataLength) + if err != nil { + return nil, nil, err + } + + accountID := a.paymentSigner.GetAccountID() + pm := &core.PaymentMetadata{ + AccountID: accountID, + BinIndex: binIndex, + CumulativePayment: cumulativePayment, + } + protoPaymentHeader := pm.ConvertToProtoPaymentHeader() + + signature, err := a.paymentSigner.SignBlobPayment(protoPaymentHeader) + if err != nil { + return nil, nil, err + } + + return protoPaymentHeader, signature, nil +} + +// TODO: PaymentCharged and SymbolsCharged copied from meterer, should be refactored +// PaymentCharged returns the chargeable price for a given data length +func (a *Accountant) PaymentCharged(dataLength uint) uint64 { + return uint64(a.SymbolsCharged(dataLength)) * uint64(a.pricePerSymbol) +} + +// SymbolsCharged returns the number of symbols charged for a given data length +// being at least MinNumSymbols or the nearest rounded-up multiple of MinNumSymbols. +func (a *Accountant) SymbolsCharged(dataLength uint) uint32 { + if dataLength <= uint(a.minNumSymbols) { + return a.minNumSymbols + } + // Round up to the nearest multiple of MinNumSymbols + return uint32(core.RoundUpDivide(uint(dataLength), uint(a.minNumSymbols))) * a.minNumSymbols +} diff --git a/api/clients/accountant_test.go b/api/clients/accountant_test.go new file mode 100644 index 0000000000..84c44fda9b --- /dev/null +++ b/api/clients/accountant_test.go @@ -0,0 +1,439 @@ +package clients + +import ( + "context" + "encoding/hex" + "math/big" + "sync" + "testing" + "time" + + "github.com/Layr-Labs/eigenda/core" + "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/core/meterer" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" +) + +func TestNewAccountant(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 100, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(500), + } + reservationWindow := uint32(6) + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + + assert.NotNil(t, accountant) + assert.Equal(t, reservation, accountant.reservation) + assert.Equal(t, onDemand, accountant.onDemand) + assert.Equal(t, reservationWindow, accountant.reservationWindow) + assert.Equal(t, pricePerSymbol, accountant.pricePerSymbol) + assert.Equal(t, minNumSymbols, accountant.minNumSymbols) + assert.Equal(t, []uint64{0, 0, 0}, accountant.binUsages) + assert.Equal(t, big.NewInt(0), accountant.cumulativePayment) +} + +func TestAccountBlob_Reservation(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 200, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(500), + } + reservationWindow := uint32(5) + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + dataLength := uint64(500) + quorums := []uint8{0, 1} + + header, _, err := accountant.AccountBlob(ctx, dataLength, quorums) + metadata := core.ConvertPaymentHeader(header) + + assert.NoError(t, err) + assert.Equal(t, meterer.GetBinIndex(uint64(time.Now().Unix()), reservationWindow), header.BinIndex) + assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) + assert.Equal(t, []uint64{500, 0, 0}, accountant.binUsages) + + dataLength = uint64(700) + + header, _, err = accountant.AccountBlob(ctx, dataLength, quorums) + metadata = core.ConvertPaymentHeader(header) + + assert.NoError(t, err) + assert.NotEqual(t, 0, header.BinIndex) + assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) + assert.Equal(t, []uint64{1200, 0, 200}, accountant.binUsages) + + // Second call should use on-demand payment + header, _, err = accountant.AccountBlob(ctx, 300, quorums) + metadata = core.ConvertPaymentHeader(header) + + assert.NoError(t, err) + assert.Equal(t, uint32(0), header.BinIndex) + assert.Equal(t, big.NewInt(3), metadata.CumulativePayment) +} + +func TestAccountBlob_OnDemand(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 200, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(500), + } + reservationWindow := uint32(5) + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + dataLength := uint64(1500) + quorums := []uint8{0, 1} + + header, _, err := accountant.AccountBlob(ctx, dataLength, quorums) + metadata := core.ConvertPaymentHeader(header) + expectedPayment := big.NewInt(int64(dataLength * uint64(pricePerSymbol) / uint64(minNumSymbols))) + assert.NoError(t, err) + assert.Equal(t, uint32(0), header.BinIndex) + assert.Equal(t, expectedPayment, metadata.CumulativePayment) + assert.Equal(t, []uint64{0, 0, 0}, accountant.binUsages) + assert.Equal(t, expectedPayment, accountant.cumulativePayment) +} + +func TestAccountBlob_InsufficientOnDemand(t *testing.T) { + reservation := core.ActiveReservation{} + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(500), + } + reservationWindow := uint32(60) + pricePerSymbol := uint32(100) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + dataLength := uint64(2000) + quorums := []uint8{0, 1} + + _, _, err = accountant.AccountBlob(ctx, dataLength, quorums) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "Accountant cannot approve payment for this blob") +} + +func TestAccountBlobCallSeries(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 200, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(1000), + } + reservationWindow := uint32(5) + pricePerSymbol := uint32(100) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + quorums := []uint8{0, 1} + now := time.Now().Unix() + + // First call: Use reservation + header, _, err := accountant.AccountBlob(ctx, 800, quorums) + metadata := core.ConvertPaymentHeader(header) + assert.NoError(t, err) + assert.Equal(t, meterer.GetBinIndex(uint64(now), reservationWindow), header.BinIndex) + assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) + + // Second call: Use remaining reservation + overflow + header, _, err = accountant.AccountBlob(ctx, 300, quorums) + metadata = core.ConvertPaymentHeader(header) + assert.NoError(t, err) + assert.Equal(t, meterer.GetBinIndex(uint64(now), reservationWindow), header.BinIndex) + assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) + + // Third call: Use on-demand + header, _, err = accountant.AccountBlob(ctx, 500, quorums) + metadata = core.ConvertPaymentHeader(header) + assert.NoError(t, err) + assert.Equal(t, uint32(0), header.BinIndex) + assert.Equal(t, big.NewInt(500), metadata.CumulativePayment) + + // Fourth call: Insufficient on-demand + _, _, err = accountant.AccountBlob(ctx, 600, quorums) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Accountant cannot approve payment for this blob") +} + +func TestAccountBlob_BinRotation(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 1000, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(1000), + } + reservationWindow := uint32(1) // Set to 1 second for testing + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + quorums := []uint8{0, 1} + + // First call + _, _, err = accountant.AccountBlob(ctx, 800, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) + + // Wait for bin rotation + time.Sleep(2 * time.Second) + + // Second call after bin rotation + _, _, err = accountant.AccountBlob(ctx, 300, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{300, 0, 0}, accountant.binUsages) + + // Third call + _, _, err = accountant.AccountBlob(ctx, 500, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) +} + +func TestBinRotation(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 1000, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(1000), + } + reservationWindow := uint32(1) // Set to 1 second for testing + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + quorums := []uint8{0, 1} + + // First call + _, _, err = accountant.AccountBlob(ctx, 800, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) + + // Second call for overflow + _, _, err = accountant.AccountBlob(ctx, 800, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{1600, 0, 600}, accountant.binUsages) + + // Wait for bin rotation + time.Sleep(1200 * time.Millisecond) + + _, _, err = accountant.AccountBlob(ctx, 300, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{300, 600, 0}, accountant.binUsages) + + // another bin rotation + time.Sleep(1200 * time.Millisecond) + + _, _, err = accountant.AccountBlob(ctx, 500, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{1100, 0, 100}, accountant.binUsages) +} + +func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 1000, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(1000), + } + reservationWindow := uint32(1) // Set to 1 second for testing + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + quorums := []uint8{0, 1} + + // Start concurrent AccountBlob calls + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 5; j++ { + _, _, err := accountant.AccountBlob(ctx, 100, quorums) + assert.NoError(t, err) + time.Sleep(500 * time.Millisecond) + } + }() + } + + // Wait for all goroutines to finish + wg.Wait() + + // Check final state + assert.Equal(t, uint64(1000), accountant.binUsages[0]+accountant.binUsages[1]+accountant.binUsages[2]) +} + +func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 200, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(1000), + } + reservationWindow := uint32(5) + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + ctx := context.Background() + quorums := []uint8{0, 1} + now := time.Now().Unix() + + // Okay reservation + header, _, err := accountant.AccountBlob(ctx, 800, quorums) + assert.NoError(t, err) + assert.Equal(t, meterer.GetBinIndex(uint64(now), reservationWindow), header.BinIndex) + metadata := core.ConvertPaymentHeader(header) + assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) + assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) + + // Second call: Allow one overflow + header, _, err = accountant.AccountBlob(ctx, 500, quorums) + assert.NoError(t, err) + metadata = core.ConvertPaymentHeader(header) + assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) + assert.Equal(t, []uint64{1300, 0, 300}, accountant.binUsages) + + // Third call: Should use on-demand payment + header, _, err = accountant.AccountBlob(ctx, 200, quorums) + assert.NoError(t, err) + assert.Equal(t, uint32(0), header.BinIndex) + metadata = core.ConvertPaymentHeader(header) + assert.Equal(t, big.NewInt(2), metadata.CumulativePayment) + assert.Equal(t, []uint64{1300, 0, 300}, accountant.binUsages) +} + +func TestAccountBlob_ReservationOverflowReset(t *testing.T) { + reservation := core.ActiveReservation{ + SymbolsPerSec: 1000, + StartTimestamp: 100, + EndTimestamp: 200, + QuorumSplit: []byte{50, 50}, + QuorumNumbers: []uint8{0, 1}, + } + onDemand := core.OnDemandPayment{ + CumulativePayment: big.NewInt(1000), + } + reservationWindow := uint32(1) // Set to 1 second for testing + pricePerSymbol := uint32(1) + minNumSymbols := uint32(100) + + privateKey1, err := crypto.GenerateKey() + assert.NoError(t, err) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + defer accountant.Stop() + + ctx := context.Background() + quorums := []uint8{0, 1} + + // full reservation + _, _, err = accountant.AccountBlob(ctx, 1000, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{1000, 0, 0}, accountant.binUsages) + + // no overflow + header, _, err := accountant.AccountBlob(ctx, 500, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{1000, 0, 0}, accountant.binUsages) + metadata := core.ConvertPaymentHeader(header) + assert.Equal(t, big.NewInt(5), metadata.CumulativePayment) + + // Wait for bin rotation + time.Sleep(1500 * time.Millisecond) + + // Third call: Should use new bin and allow overflow again + header, _, err = accountant.AccountBlob(ctx, 500, quorums) + assert.NoError(t, err) + assert.Equal(t, []uint64{500, 0, 0}, accountant.binUsages) +} diff --git a/api/clients/disperser_client.go b/api/clients/disperser_client.go index c2422ea00c..69f72a5545 100644 --- a/api/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -9,6 +9,7 @@ import ( "github.com/Layr-Labs/eigenda/api" disperser_rpc "github.com/Layr-Labs/eigenda/api/grpc/disperser" + "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/disperser" "github.com/Layr-Labs/eigenda/encoding" @@ -78,6 +79,8 @@ type disperserClient struct { // instead of a real network connection for eg. conn *grpc.ClientConn client disperser_rpc.DisperserClient + + accountant *Accountant } var _ DisperserClient = &disperserClient{} @@ -102,7 +105,7 @@ var _ DisperserClient = &disperserClient{} // // // Subsequent calls will use the existing connection // status2, requestId2, err := client.DisperseBlob(ctx, otherData, otherQuorums) -func NewDisperserClient(config *Config, signer core.BlobRequestSigner) (*disperserClient, error) { +func NewDisperserClient(config *Config, signer core.BlobRequestSigner, accountant *Accountant) (*disperserClient, error) { if err := checkConfigAndSetDefaults(config); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } @@ -110,6 +113,8 @@ func NewDisperserClient(config *Config, signer core.BlobRequestSigner) (*dispers config: config, signer: signer, // conn and client are initialized lazily + + accountant: accountant, }, nil } @@ -183,9 +188,50 @@ func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, quorums return blobStatus, reply.GetRequestId(), nil } -// TODO: implemented in subsequent PR +// DispersePaidBlob disperses a blob with a payment header and signature. Similar to DisperseBlob but with signed payment header. func (c *disperserClient) DispersePaidBlob(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { - return nil, nil, api.NewErrorInternal("not implemented") + err := c.initOnceGrpcConnection() + if err != nil { + return nil, nil, fmt.Errorf("error initializing connection: %w", err) + } + + ctxTimeout, cancel := context.WithTimeout(ctx, c.config.Timeout) + defer cancel() + + quorumNumbers := make([]uint32, len(quorums)) + for i, q := range quorums { + quorumNumbers[i] = uint32(q) + } + + // check every 32 bytes of data are within the valid range for a bn254 field element + _, err = rs.ToFrArray(data) + if err != nil { + return nil, nil, fmt.Errorf("encountered an error to convert a 32-bytes into a valid field element, please use the correct format where every 32bytes(big-endian) is less than 21888242871839275222246405745257275088548364400416034343698204186575808495617 %w", err) + } + + header, signature, err := c.accountant.AccountBlob(ctx, uint64(len(data)), quorums) + if err != nil { + return nil, nil, err + } + + request := &disperser_rpc.DispersePaidBlobRequest{ + Data: data, + QuorumNumbers: quorumNumbers, + PaymentHeader: header, + PaymentSignature: signature, + } + + reply, err := c.client.DispersePaidBlob(ctxTimeout, request) + if err != nil { + return nil, nil, err + } + + blobStatus, err := disperser.FromBlobStatusProto(reply.GetResult()) + if err != nil { + return nil, nil, err + } + + return blobStatus, reply.GetRequestId(), nil } func (c *disperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go index 81d7716807..1df99b1a00 100644 --- a/api/clients/eigenda_client.go +++ b/api/clients/eigenda_client.go @@ -21,6 +21,7 @@ import ( edasm "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/disperser" ) // IEigenDAClient is a wrapper around the DisperserClient interface which @@ -34,7 +35,8 @@ type IEigenDAClient interface { // See the NewEigenDAClient constructor's documentation for details and usage examples. // TODO: Refactor this struct and interface above to use same naming convention as disperser client. -// Also need to make the fields private and use the constructor in the tests. +// +// Also need to make the fields private and use the constructor in the tests. type EigenDAClient struct { // TODO: all of these should be private, to prevent users from using them directly, // which breaks encapsulation and makes it hard for us to do refactors or changes @@ -44,6 +46,7 @@ type EigenDAClient struct { ethClient *ethclient.Client edasmCaller *edasm.ContractEigenDAServiceManagerCaller Codec codecs.BlobCodec + accountant Accountant } var _ IEigenDAClient = &EigenDAClient{} @@ -108,7 +111,13 @@ func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClien } disperserConfig := NewConfig(host, port, config.ResponseTimeout, !config.DisableTLS) - disperserClient, err := NewDisperserClient(disperserConfig, signer) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString([]byte(config.SignerPrivateKeyHex))) + if err != nil { + return nil, fmt.Errorf("new payment signer: %w", err) + } + // a subsequent PR contains updates to fill in payment state + accountant := NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 0, 0, 0, paymentSigner) + disperserClient, err := NewDisperserClient(disperserConfig, signer, accountant) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) } @@ -369,6 +378,125 @@ func (m *EigenDAClient) putBlob(ctxFinality context.Context, rawData []byte, res } } +// PaidPutBlob behaves like PutBlob but with authenticated payment. +func (m EigenDAClient) PaidPutBlob(ctx context.Context, data []byte) (*grpcdisperser.BlobInfo, error) { + resultChan, errorChan := m.PaidPutBlobAsync(ctx, data) + select { // no timeout here because we depend on the configured timeout in PutBlobAsync + case result := <-resultChan: + return result, nil + case err := <-errorChan: + return nil, err + } +} + +func (m EigenDAClient) PaidPutBlobAsync(ctx context.Context, data []byte) (resultChan chan *grpcdisperser.BlobInfo, errChan chan error) { + resultChan = make(chan *grpcdisperser.BlobInfo, 1) + errChan = make(chan error, 1) + go m.paidPutBlob(ctx, data, resultChan, errChan) + return +} + +func (m EigenDAClient) paidPutBlob(ctx context.Context, rawData []byte, resultChan chan *grpcdisperser.BlobInfo, errChan chan error) { + m.Log.Info("Attempting to disperse blob to EigenDA with payment") + + // encode blob + if m.Codec == nil { + errChan <- fmt.Errorf("Codec cannot be nil") + return + } + + data, err := m.Codec.EncodeBlob(rawData) + if err != nil { + errChan <- fmt.Errorf("error encoding blob: %w", err) + return + } + + customQuorumNumbers := make([]uint8, len(m.Config.CustomQuorumIDs)) + for i, e := range m.Config.CustomQuorumIDs { + customQuorumNumbers[i] = uint8(e) + } + // disperse blob + blobStatus, requestID, err := m.Client.DispersePaidBlob(ctx, data, customQuorumNumbers) + if err != nil { + errChan <- fmt.Errorf("error initializing DispersePaidBlob() client: %w", err) + return + } + + // process response + if *blobStatus == disperser.Failed { + m.Log.Error("Unable to disperse blob to EigenDA, aborting", "err", err) + errChan <- fmt.Errorf("reply status is %d", blobStatus) + return + } + + base64RequestID := base64.StdEncoding.EncodeToString(requestID) + m.Log.Info("Blob dispersed to EigenDA, now waiting for confirmation", "requestID", base64RequestID) + + ticker := time.NewTicker(m.Config.StatusQueryRetryInterval) + defer ticker.Stop() + + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, m.Config.StatusQueryTimeout) + defer cancel() + + alreadyWaitingForDispersal := false + alreadyWaitingForFinalization := false + for { + select { + case <-ctx.Done(): + errChan <- fmt.Errorf("timed out waiting for EigenDA blob to confirm blob with request id=%s: %w", base64RequestID, ctx.Err()) + return + case <-ticker.C: + statusRes, err := m.Client.GetBlobStatus(ctx, requestID) + if err != nil { + m.Log.Error("Unable to retrieve blob dispersal status, will retry", "requestID", base64RequestID, "err", err) + continue + } + + switch statusRes.Status { + case grpcdisperser.BlobStatus_PROCESSING, grpcdisperser.BlobStatus_DISPERSING: + // to prevent log clutter, we only log at info level once + if alreadyWaitingForDispersal { + m.Log.Debug("Blob submitted, waiting for dispersal from EigenDA", "requestID", base64RequestID) + } else { + m.Log.Info("Blob submitted, waiting for dispersal from EigenDA", "requestID", base64RequestID) + alreadyWaitingForDispersal = true + } + case grpcdisperser.BlobStatus_FAILED: + m.Log.Error("EigenDA blob dispersal failed in processing", "requestID", base64RequestID, "err", err) + errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing, requestID=%s: %w", base64RequestID, err) + return + case grpcdisperser.BlobStatus_INSUFFICIENT_SIGNATURES: + m.Log.Error("EigenDA blob dispersal failed in processing with insufficient signatures", "requestID", base64RequestID, "err", err) + errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing with insufficient signatures, requestID=%s: %w", base64RequestID, err) + return + case grpcdisperser.BlobStatus_CONFIRMED: + if m.Config.WaitForFinalization { + // to prevent log clutter, we only log at info level once + if alreadyWaitingForFinalization { + m.Log.Debug("EigenDA blob confirmed, waiting for finalization", "requestID", base64RequestID) + } else { + m.Log.Info("EigenDA blob confirmed, waiting for finalization", "requestID", base64RequestID) + alreadyWaitingForFinalization = true + } + } else { + m.Log.Info("EigenDA blob confirmed", "requestID", base64RequestID) + resultChan <- statusRes.Info + return + } + case grpcdisperser.BlobStatus_FINALIZED: + batchHeaderHashHex := fmt.Sprintf("0x%s", hex.EncodeToString(statusRes.Info.BlobVerificationProof.BatchMetadata.BatchHeaderHash)) + m.Log.Info("Successfully dispersed blob to EigenDA", "requestID", base64RequestID, "batchHeaderHash", batchHeaderHashHex) + resultChan <- statusRes.Info + return + default: + errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing with reply status %d", statusRes.Status) + return + } + } + } +} + // Close simply calls Close() on the wrapped disperserClient, to close the grpc connection to the disperser server. // It is thread safe and can be called multiple times. func (c *EigenDAClient) Close() error { diff --git a/api/clients/mock/disperser_client.go b/api/clients/mock/disperser_client.go index c7ab9627f0..6784a1afde 100644 --- a/api/clients/mock/disperser_client.go +++ b/api/clients/mock/disperser_client.go @@ -81,9 +81,25 @@ func (c *MockDisperserClient) DisperseBlob(ctx context.Context, data []byte, quo return status, key, err } -// TODO: implement in the subsequent PR func (c *MockDisperserClient) DispersePaidBlob(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { - return nil, nil, nil + args := c.Called(data, quorums) + var status *disperser.BlobStatus + if args.Get(0) != nil { + status = (args.Get(0)).(*disperser.BlobStatus) + } + var key []byte + if args.Get(1) != nil { + key = (args.Get(1)).([]byte) + } + var err error + if args.Get(2) != nil { + err = (args.Get(2)).(error) + } + + keyStr := base64.StdEncoding.EncodeToString(key) + c.mockRequestIDStore[keyStr] = data + + return status, key, err } func (c *MockDisperserClient) GetBlobStatus(ctx context.Context, key []byte) (*disperser_rpc.BlobStatusReply, error) { diff --git a/api/clients/retrieval_client.go b/api/clients/retrieval_client.go index 8bc034cd28..e8782a5e6b 100644 --- a/api/clients/retrieval_client.go +++ b/api/clients/retrieval_client.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/encoding" "github.com/Layr-Labs/eigensdk-go/logging" diff --git a/core/data.go b/core/data.go index 0742f5aa0b..970d76746f 100644 --- a/core/data.go +++ b/core/data.go @@ -572,6 +572,15 @@ func ConvertPaymentHeader(header *commonpb.PaymentHeader) *PaymentMetadata { } } +// Hash returns the Keccak256 hash of the PaymentMetadata +func (pm *PaymentMetadata) ConvertToProtoPaymentHeader() *commonpb.PaymentHeader { + return &commonpb.PaymentHeader{ + AccountId: pm.AccountID, + BinIndex: pm.BinIndex, + CumulativePayment: pm.CumulativePayment.Bytes(), + } +} + // OperatorInfo contains information about an operator which is stored on the blockchain state, // corresponding to a particular quorum type ActiveReservation struct { diff --git a/core/meterer/meterer.go b/core/meterer/meterer.go index e29cc7c1bb..964205f33d 100644 --- a/core/meterer/meterer.go +++ b/core/meterer/meterer.go @@ -70,15 +70,14 @@ func (m *Meterer) Start(ctx context.Context) { // MeterRequest validates a blob header and adds it to the meterer's state // TODO: return error if there's a rejection (with reasoning) or internal error (should be very rare) -func (m *Meterer) MeterRequest(ctx context.Context, blob core.Blob, header core.PaymentMetadata) error { - headerQuorums := blob.GetQuorumNumbers() +func (m *Meterer) MeterRequest(ctx context.Context, header core.PaymentMetadata, blobLength uint, quorumNumbers []uint8) error { // Validate against the payment method if header.CumulativePayment.Sign() == 0 { reservation, err := m.ChainPaymentState.GetActiveReservationByAccount(ctx, header.AccountID) if err != nil { return fmt.Errorf("failed to get active reservation by account: %w", err) } - if err := m.ServeReservationRequest(ctx, header, &reservation, blob.RequestHeader.BlobAuthHeader.Length, headerQuorums); err != nil { + if err := m.ServeReservationRequest(ctx, header, &reservation, blobLength, quorumNumbers); err != nil { return fmt.Errorf("invalid reservation: %w", err) } } else { @@ -86,7 +85,7 @@ func (m *Meterer) MeterRequest(ctx context.Context, blob core.Blob, header core. if err != nil { return fmt.Errorf("failed to get on-demand payment by account: %w", err) } - if err := m.ServeOnDemandRequest(ctx, header, &onDemandPayment, blob.RequestHeader.BlobAuthHeader.Length, headerQuorums); err != nil { + if err := m.ServeOnDemandRequest(ctx, header, &onDemandPayment, blobLength, quorumNumbers); err != nil { return fmt.Errorf("invalid on-demand request: %w", err) } } diff --git a/core/meterer/meterer_test.go b/core/meterer/meterer_test.go index 8fb2d1b9df..fbab39bc3c 100644 --- a/core/meterer/meterer_test.go +++ b/core/meterer/meterer_test.go @@ -15,7 +15,6 @@ import ( "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/meterer" "github.com/Layr-Labs/eigenda/core/mock" - "github.com/Layr-Labs/eigenda/encoding" "github.com/Layr-Labs/eigenda/inabox/deploy" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/ethereum/go-ethereum/crypto" @@ -185,17 +184,17 @@ func TestMetererReservations(t *testing.T) { paymentChainState.On("GetActiveReservationByAccount", testifymock.Anything, testifymock.Anything).Return(core.ActiveReservation{}, fmt.Errorf("reservation not found")) // test invalid quorom ID - blob, header := createMetererInput(1, 0, 1000, []uint8{0, 1, 2}, accountID1) - err := mt.MeterRequest(ctx, *blob, *header) + header := createPaymentHeader(1, 0, accountID1) + err := mt.MeterRequest(ctx, *header, 1000, []uint8{0, 1, 2}) assert.ErrorContains(t, err, "quorum number mismatch") // overwhelming bin overflow for empty bins - blob, header = createMetererInput(binIndex-1, 0, 10, quoromNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex-1, 0, accountID2) + err = mt.MeterRequest(ctx, *header, 10, quoromNumbers) assert.NoError(t, err) // overwhelming bin overflow for empty bins - blob, header = createMetererInput(binIndex-1, 0, 1000, quoromNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex-1, 0, accountID2) + err = mt.MeterRequest(ctx, *header, 1000, quoromNumbers) assert.ErrorContains(t, err, "overflow usage exceeds bin limit") // test non-existent account @@ -203,22 +202,22 @@ func TestMetererReservations(t *testing.T) { if err != nil { t.Fatalf("Failed to generate key: %v", err) } - blob, header = createMetererInput(1, 0, 1000, []uint8{0, 1, 2}, crypto.PubkeyToAddress(unregisteredUser.PublicKey).Hex()) + header = createPaymentHeader(1, 0, crypto.PubkeyToAddress(unregisteredUser.PublicKey).Hex()) assert.NoError(t, err) - err = mt.MeterRequest(ctx, *blob, *header) + err = mt.MeterRequest(ctx, *header, 1000, []uint8{0, 1, 2}) assert.ErrorContains(t, err, "failed to get active reservation by account: reservation not found") // test invalid bin index - blob, header = createMetererInput(binIndex, 0, 2000, quoromNumbers, accountID1) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, 0, accountID1) + err = mt.MeterRequest(ctx, *header, 2000, quoromNumbers) assert.ErrorContains(t, err, "invalid bin index for reservation") // test bin usage metering dataLength := uint(20) requiredLength := uint(21) // 21 should be charged for length of 20 since minNumSymbols is 3 for i := 0; i < 9; i++ { - blob, header = createMetererInput(binIndex, 0, dataLength, quoromNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, 0, accountID2) + err = mt.MeterRequest(ctx, *header, dataLength, quoromNumbers) assert.NoError(t, err) item, err := dynamoClient.GetItem(ctx, reservationTableName, commondynamodb.Key{ "AccountID": &types.AttributeValueMemberS{Value: accountID2}, @@ -231,9 +230,9 @@ func TestMetererReservations(t *testing.T) { } // first over flow is allowed - blob, header = createMetererInput(binIndex, 0, 25, quoromNumbers, accountID2) + header = createPaymentHeader(binIndex, 0, accountID2) assert.NoError(t, err) - err = mt.MeterRequest(ctx, *blob, *header) + err = mt.MeterRequest(ctx, *header, 25, quoromNumbers) assert.NoError(t, err) overflowedBinIndex := binIndex + 2 item, err := dynamoClient.GetItem(ctx, reservationTableName, commondynamodb.Key{ @@ -247,9 +246,9 @@ func TestMetererReservations(t *testing.T) { assert.Equal(t, strconv.Itoa(int(16)), item["BinUsage"].(*types.AttributeValueMemberN).Value) // second over flow - blob, header = createMetererInput(binIndex, 0, 1, quoromNumbers, accountID2) + header = createPaymentHeader(binIndex, 0, accountID2) assert.NoError(t, err) - err = mt.MeterRequest(ctx, *blob, *header) + err = mt.MeterRequest(ctx, *header, 1, quoromNumbers) assert.ErrorContains(t, err, "bin has already been filled") } @@ -274,19 +273,19 @@ func TestMetererOnDemand(t *testing.T) { if err != nil { t.Fatalf("Failed to generate key: %v", err) } - blob, header := createMetererInput(binIndex, 2, 1000, quorumNumbers, crypto.PubkeyToAddress(unregisteredUser.PublicKey).Hex()) + header := createPaymentHeader(binIndex, 2, crypto.PubkeyToAddress(unregisteredUser.PublicKey).Hex()) assert.NoError(t, err) - err = mt.MeterRequest(ctx, *blob, *header) + err = mt.MeterRequest(ctx, *header, 1000, quorumNumbers) assert.ErrorContains(t, err, "failed to get on-demand payment by account: payment not found") // test invalid quorom ID - blob, header = createMetererInput(binIndex, 1, 1000, []uint8{0, 1, 2}, accountID1) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, 1, accountID1) + err = mt.MeterRequest(ctx, *header, 1000, []uint8{0, 1, 2}) assert.ErrorContains(t, err, "invalid quorum for On-Demand Request") // test insufficient cumulative payment - blob, header = createMetererInput(binIndex, 1, 2000, quorumNumbers, accountID1) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, 1, accountID1) + err = mt.MeterRequest(ctx, *header, 1000, quorumNumbers) assert.ErrorContains(t, err, "insufficient cumulative payment increment") // No rollback after meter request result, err := dynamoClient.Query(ctx, ondemandTableName, "AccountID = :account", commondynamodb.ExpressionValues{ @@ -300,37 +299,37 @@ func TestMetererOnDemand(t *testing.T) { dataLength := uint(100) priceCharged := mt.PaymentCharged(dataLength) assert.Equal(t, uint64(102*mt.ChainPaymentState.GetPricePerSymbol()), priceCharged) - blob, header = createMetererInput(binIndex, priceCharged, dataLength, quorumNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, priceCharged, accountID2) + err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) assert.NoError(t, err) - blob, header = createMetererInput(binIndex, priceCharged, dataLength, quorumNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, priceCharged, accountID2) + err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) assert.ErrorContains(t, err, "exact payment already exists") // test valid payments for i := 1; i < 9; i++ { - blob, header = createMetererInput(binIndex, uint64(priceCharged)*uint64(i+1), dataLength, quorumNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, uint64(priceCharged)*uint64(i+1), accountID2) + err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) assert.NoError(t, err) } // test cumulative payment on-chain constraint - blob, header = createMetererInput(binIndex, 2023, 1, quorumNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, 2023, accountID2) + err = mt.MeterRequest(ctx, *header, 1, quorumNumbers) assert.ErrorContains(t, err, "invalid on-demand payment: request claims a cumulative payment greater than the on-chain deposit") // test insufficient increment in cumulative payment previousCumulativePayment := uint64(priceCharged) * uint64(9) dataLength = uint(2) priceCharged = mt.PaymentCharged(dataLength) - blob, header = createMetererInput(binIndex, previousCumulativePayment+priceCharged-1, dataLength, quorumNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, previousCumulativePayment+priceCharged-1, accountID2) + err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) assert.ErrorContains(t, err, "invalid on-demand payment: insufficient cumulative payment increment") previousCumulativePayment = previousCumulativePayment + priceCharged // test cannot insert cumulative payment in out of order - blob, header = createMetererInput(binIndex, mt.PaymentCharged(50), 50, quorumNumbers, accountID2) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, mt.PaymentCharged(50), accountID2) + err = mt.MeterRequest(ctx, *header, 50, quorumNumbers) assert.ErrorContains(t, err, "invalid on-demand payment: breaking cumulative payment invariants") numPrevRecords := 12 @@ -341,9 +340,8 @@ func TestMetererOnDemand(t *testing.T) { assert.NoError(t, err) assert.Equal(t, numPrevRecords, len(result)) // test failed global rate limit (previously payment recorded: 2, global limit: 1009) - fmt.Println("need ", previousCumulativePayment+mt.PaymentCharged(1010)) - blob, header = createMetererInput(binIndex, previousCumulativePayment+mt.PaymentCharged(1010), 1010, quorumNumbers, accountID1) - err = mt.MeterRequest(ctx, *blob, *header) + header = createPaymentHeader(binIndex, previousCumulativePayment+mt.PaymentCharged(1010), accountID1) + err = mt.MeterRequest(ctx, *header, 1010, quorumNumbers) assert.ErrorContains(t, err, "failed global rate limiting") // Correct rollback result, err = dynamoClient.Query(ctx, ondemandTableName, "AccountID = :account", commondynamodb.ExpressionValues{ @@ -465,28 +463,10 @@ func TestMeterer_symbolsCharged(t *testing.T) { } } -func createMetererInput(binIndex uint32, cumulativePayment uint64, dataLength uint, quorumNumbers []uint8, accountID string) (blob *core.Blob, header *core.PaymentMetadata) { - sp := make([]*core.SecurityParam, len(quorumNumbers)) - for i, quorumID := range quorumNumbers { - sp[i] = &core.SecurityParam{ - QuorumID: quorumID, - } - } - blob = &core.Blob{ - RequestHeader: core.BlobRequestHeader{ - BlobAuthHeader: core.BlobAuthHeader{ - AccountID: accountID2, - BlobCommitments: encoding.BlobCommitments{ - Length: dataLength, - }, - }, - SecurityParams: sp, - }, - } - header = &core.PaymentMetadata{ +func createPaymentHeader(binIndex uint32, cumulativePayment uint64, accountID string) *core.PaymentMetadata { + return &core.PaymentMetadata{ AccountID: accountID, BinIndex: binIndex, CumulativePayment: big.NewInt(int64(cumulativePayment)), } - return blob, header } diff --git a/disperser/apiserver/payment_test.go b/disperser/apiserver/payment_test.go new file mode 100644 index 0000000000..8f57e7ea0e --- /dev/null +++ b/disperser/apiserver/payment_test.go @@ -0,0 +1,150 @@ +package apiserver_test + +import ( + "context" + "crypto/rand" + "math/big" + "net" + "testing" + "time" + + "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/core/meterer" + "github.com/Layr-Labs/eigenda/core/mock" + "github.com/Layr-Labs/eigenda/encoding/utils/codec" + + pbcommon "github.com/Layr-Labs/eigenda/api/grpc/common" + pb "github.com/Layr-Labs/eigenda/api/grpc/disperser" + "github.com/Layr-Labs/eigenda/core" + "github.com/stretchr/testify/assert" + tmock "github.com/stretchr/testify/mock" + "google.golang.org/grpc/peer" +) + +func TestDispersePaidBlob(t *testing.T) { + + transactor := &mock.MockWriter{} + transactor.On("GetCurrentBlockNumber").Return(uint32(100), nil) + transactor.On("GetQuorumCount").Return(uint8(2), nil) + quorumParams := []core.SecurityParam{ + {QuorumID: 0, AdversaryThreshold: 80, ConfirmationThreshold: 100}, + {QuorumID: 1, AdversaryThreshold: 80, ConfirmationThreshold: 100}, + } + transactor.On("GetQuorumSecurityParams", tmock.Anything).Return(quorumParams, nil) + transactor.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{0, 1}, nil) + + quorums := []uint32{0, 1} + + dispersalServer := newTestServer(transactor, t.Name()) + + data := make([]byte, 1024) + _, err := rand.Read(data) + assert.NoError(t, err) + + data = codec.ConvertByPaddingEmptyByte(data) + + p := &peer.Peer{ + Addr: &net.TCPAddr{ + IP: net.ParseIP("0.0.0.0"), + Port: 51001, + }, + } + ctx := peer.NewContext(context.Background(), p) + + transactor.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{0, 1}, nil).Twice() + + pk := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeb" + signer := auth.NewPaymentSigner(pk) + + dataLength := len(data) + // disperse on-demand payment + for i := 1; i < 3; i++ { + pm := pbcommon.PaymentHeader{ + AccountId: signer.GetAccountID(), + BinIndex: 0, + CumulativePayment: big.NewInt(int64(dataLength * i)).Bytes(), + } + sig, err := signer.SignBlobPayment(&pm) + assert.NoError(t, err) + reply, err := dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ + Data: data, + QuorumNumbers: quorums, + PaymentHeader: &pm, + PaymentSignature: sig, + }) + assert.NoError(t, err) + assert.Equal(t, reply.GetResult(), pb.BlobStatus_PROCESSING) + assert.NotNil(t, reply.GetRequestId()) + } + + // exceeded payment limit + pm := pbcommon.PaymentHeader{ + AccountId: signer.GetAccountID(), + BinIndex: 0, + CumulativePayment: big.NewInt(int64(dataLength*3) - 1).Bytes(), + } + sig, err := signer.SignBlobPayment(&pm) + assert.NoError(t, err) + _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ + Data: data, + QuorumNumbers: quorums, + PaymentHeader: &pm, + PaymentSignature: sig, + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "request claims a cumulative payment greater than the on-chain deposit") + + // disperse paid reservation (any quorum number) + // TODO: somehow meterer is not defined as a method or field in dispersalServer; reservationWindow we set was 1 + for i := 0; i < 2; i++ { + binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), 1) + pm = pbcommon.PaymentHeader{ + AccountId: signer.GetAccountID(), + BinIndex: binIndex, + CumulativePayment: big.NewInt(0).Bytes(), + } + sig, err = signer.SignBlobPayment(&pm) + assert.NoError(t, err) + reply, err := dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ + Data: data, + QuorumNumbers: []uint32{1}, + PaymentHeader: &pm, + PaymentSignature: sig, + }) + assert.NoError(t, err) + assert.Equal(t, reply.GetResult(), pb.BlobStatus_PROCESSING) + assert.NotNil(t, reply.GetRequestId()) + + } + binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), 1) + pm = pbcommon.PaymentHeader{ + AccountId: signer.GetAccountID(), + BinIndex: binIndex, + CumulativePayment: big.NewInt(0).Bytes(), + } + sig, err = signer.SignBlobPayment(&pm) + assert.NoError(t, err) + _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ + Data: data, + QuorumNumbers: []uint32{1}, + PaymentHeader: &pm, + PaymentSignature: sig, + }) + assert.Contains(t, err.Error(), "bin has already been filled") + + // invalid bin index + binIndex = meterer.GetBinIndex(uint64(time.Now().Unix())/2, 1) + pm = pbcommon.PaymentHeader{ + AccountId: signer.GetAccountID(), + BinIndex: binIndex, + CumulativePayment: big.NewInt(0).Bytes(), + } + sig, err = signer.SignBlobPayment(&pm) + _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ + Data: data, + QuorumNumbers: []uint32{1}, + PaymentHeader: &pm, + PaymentSignature: sig, + }) + assert.Contains(t, err.Error(), "invalid bin index for reservation") +} diff --git a/disperser/apiserver/server.go b/disperser/apiserver/server.go index e499920c78..8a86280fd2 100644 --- a/disperser/apiserver/server.go +++ b/disperser/apiserver/server.go @@ -285,7 +285,7 @@ func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, aut // If paymentHeader is not empty, we use the meterer, otherwise we use the ratelimiter if the ratelimiter is available if paymentHeader != nil { - err := s.meterer.MeterRequest(ctx, *blob, *paymentHeader) + err := s.meterer.MeterRequest(ctx, *paymentHeader, uint(blobSize), blob.GetQuorumNumbers()) if err != nil { return nil, api.NewErrorResourceExhausted(err.Error()) } @@ -1084,6 +1084,7 @@ func (s *DispersalServer) validatePaidRequestAndGetBlob(ctx context.Context, req data := req.GetData() blobSize := len(data) + // The blob size in bytes must be in range [1, maxBlobSize]. if blobSize > s.maxBlobSize { return nil, fmt.Errorf("blob size cannot exceed %v Bytes", s.maxBlobSize) diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index 4f6535769a..5cfcb448f5 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -5,6 +5,8 @@ import ( "crypto/rand" "flag" "fmt" + "math" + "math/big" "net" "os" "runtime" @@ -701,6 +703,22 @@ func newTestServer(transactor core.Writer, testName string) *apiserver.Dispersal panic("failed to make initial query to the on-chain state") } + mockState.On("GetPricePerSymbol").Return(uint32(1), nil) + mockState.On("GetMinNumSymbols").Return(uint32(1), nil) + mockState.On("GetGlobalSymbolsPerSecond").Return(uint64(4096), nil) + mockState.On("GetRequiredQuorumNumbers").Return([]uint8{0, 1}, nil) + mockState.On("GetOnDemandQuorumNumbers").Return([]uint8{0, 1}, nil) + mockState.On("GetReservationWindow").Return(uint32(1), nil) + mockState.On("GetOnDemandPaymentByAccount", tmock.Anything, tmock.Anything).Return(core.OnDemandPayment{ + CumulativePayment: big.NewInt(3000), + }, nil) + mockState.On("GetActiveReservationByAccount", tmock.Anything, tmock.Anything).Return(core.ActiveReservation{ + SymbolsPerSec: 2048, + StartTimestamp: 0, + EndTimestamp: math.MaxUint32, + QuorumNumbers: []uint8{0, 1}, + QuorumSplit: []byte{50, 50}, + }, nil) // append test name to each table name for an unique store table_names := []string{"reservations_server_" + testName, "ondemand_server_" + testName, "global_server_" + testName} err = meterer.CreateReservationTable(awsConfig, table_names[0]) @@ -730,7 +748,8 @@ func newTestServer(transactor core.Writer, testName string) *apiserver.Dispersal teardown() panic("failed to create offchain store") } - meterer := meterer.NewMeterer(meterer.Config{}, mockState, store, logger) + mt := meterer.NewMeterer(meterer.Config{}, mockState, store, logger) + mt.ChainPaymentState.RefreshOnchainPaymentState(context.Background(), nil) ratelimiter := ratelimit.NewRateLimiter(prometheus.NewRegistry(), globalParams, bucketStore, logger) rateConfig := apiserver.RateConfig{ @@ -787,7 +806,7 @@ func newTestServer(transactor core.Writer, testName string) *apiserver.Dispersal return apiserver.NewDispersalServer(disperser.ServerConfig{ GrpcPort: "51001", GrpcTimeout: 1 * time.Second, - }, queue, transactor, logger, disperser.NewMetrics(prometheus.NewRegistry(), "9001", logger), meterer, ratelimiter, rateConfig, testMaxBlobSize) + }, queue, transactor, logger, disperser.NewMetrics(prometheus.NewRegistry(), "9001", logger), mt, ratelimiter, rateConfig, testMaxBlobSize) } func disperseBlob(t *testing.T, server *apiserver.DispersalServer, data []byte) (pb.BlobStatus, uint, []byte) { diff --git a/inabox/deploy/config.go b/inabox/deploy/config.go index 01795d65fe..1de79f0032 100644 --- a/inabox/deploy/config.go +++ b/inabox/deploy/config.go @@ -159,16 +159,20 @@ func (env *Config) generateChurnerVars(ind int, graphUrl, logPath, grpcPort stri // Generates disperser .env func (env *Config) generateDisperserVars(ind int, key, address, logPath, dbPath, grpcPort string) DisperserVars { v := DisperserVars{ - DISPERSER_SERVER_S3_BUCKET_NAME: "test-eigenda-blobstore", - DISPERSER_SERVER_DYNAMODB_TABLE_NAME: "test-BlobMetadata", - DISPERSER_SERVER_RATE_BUCKET_TABLE_NAME: "", - DISPERSER_SERVER_RATE_BUCKET_STORE_SIZE: "100000", - DISPERSER_SERVER_GRPC_PORT: grpcPort, - DISPERSER_SERVER_ENABLE_METRICS: "true", - DISPERSER_SERVER_METRICS_HTTP_PORT: "9093", - DISPERSER_SERVER_CHAIN_RPC: "", - DISPERSER_SERVER_PRIVATE_KEY: "123", - DISPERSER_SERVER_NUM_CONFIRMATIONS: "0", + DISPERSER_SERVER_S3_BUCKET_NAME: "test-eigenda-blobstore", + DISPERSER_SERVER_DYNAMODB_TABLE_NAME: "test-BlobMetadata", + DISPERSER_SERVER_RATE_BUCKET_TABLE_NAME: "", + DISPERSER_SERVER_RATE_BUCKET_STORE_SIZE: "100000", + DISPERSER_SERVER_GRPC_PORT: grpcPort, + DISPERSER_SERVER_ENABLE_METRICS: "true", + DISPERSER_SERVER_ENABLE_PAYMENT_METERER: "true", + DISPERSER_SERVER_RESERVATIONS_TABLE_NAME: "reservations_inabox", + DISPERSER_SERVER_ON_DEMAND_TABLE_NAME: "on_demand_inabox", + DISPERSER_SERVER_GLOBAL_RATE_TABLE_NAME: "global_rate_inabox", + DISPERSER_SERVER_METRICS_HTTP_PORT: "9093", + DISPERSER_SERVER_CHAIN_RPC: "", + DISPERSER_SERVER_PRIVATE_KEY: "123", + DISPERSER_SERVER_NUM_CONFIRMATIONS: "0", DISPERSER_SERVER_REGISTERED_QUORUM_ID: "0,1", DISPERSER_SERVER_TOTAL_UNAUTH_BYTE_RATE: "10000000,10000000", diff --git a/inabox/deploy/env_vars.go b/inabox/deploy/env_vars.go index f701ce64d1..2c654b17d5 100644 --- a/inabox/deploy/env_vars.go +++ b/inabox/deploy/env_vars.go @@ -76,6 +76,14 @@ type DisperserVars struct { DISPERSER_SERVER_RETRIEVAL_BLOB_RATE string DISPERSER_SERVER_RETRIEVAL_BYTE_RATE string + + DISPERSER_SERVER_ENABLE_PAYMENT_METERER string + + DISPERSER_SERVER_RESERVATIONS_TABLE_NAME string + + DISPERSER_SERVER_ON_DEMAND_TABLE_NAME string + + DISPERSER_SERVER_GLOBAL_RATE_TABLE_NAME string } func (vars DisperserVars) getEnvMap() map[string]string { diff --git a/inabox/tests/integration_test.go b/inabox/tests/integration_test.go index f257d3e2b8..efdf70120a 100644 --- a/inabox/tests/integration_test.go +++ b/inabox/tests/integration_test.go @@ -4,14 +4,17 @@ import ( "bytes" "context" "crypto/rand" + "encoding/hex" "math/big" "time" "github.com/Layr-Labs/eigenda/api/clients" disperserpb "github.com/Layr-Labs/eigenda/api/grpc/disperser" rollupbindings "github.com/Layr-Labs/eigenda/contracts/bindings/MockRollup" + "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" "github.com/Layr-Labs/eigenda/disperser" + "github.com/ethereum/go-ethereum/crypto" "github.com/Layr-Labs/eigenda/encoding/utils/codec" . "github.com/onsi/ginkgo/v2" @@ -36,11 +39,15 @@ var _ = Describe("Inabox Integration", func() { privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" signer := auth.NewLocalBlobRequestSigner(privateKeyHex) + privateKey, err := crypto.HexToECDSA(privateKeyHex[2:]) // Remove "0x" prefix + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey.D.Bytes())) + Expect(err).To(BeNil()) + disp, err := clients.NewDisperserClient(&clients.Config{ Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer) + }, signer, clients.NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) Expect(err).To(BeNil()) Expect(disp).To(Not(BeNil())) diff --git a/inabox/tests/payment_test.go b/inabox/tests/payment_test.go new file mode 100644 index 0000000000..6da9c40c78 --- /dev/null +++ b/inabox/tests/payment_test.go @@ -0,0 +1,155 @@ +package integration_test + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/hex" + "time" + + "github.com/Layr-Labs/eigenda/api/clients" + disperserpb "github.com/Layr-Labs/eigenda/api/grpc/disperser" + "github.com/Layr-Labs/eigenda/core" + "github.com/Layr-Labs/eigenda/core/auth" + "github.com/Layr-Labs/eigenda/disperser" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/Layr-Labs/eigenda/encoding/utils/codec" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Inabox Integration", func() { + It("test payment metering", func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) + defer cancel() + + gasTipCap, gasFeeCap, err := ethClient.GetLatestGasCaps(ctx) + Expect(err).To(BeNil()) + + privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" + signer := auth.NewLocalBlobRequestSigner(privateKeyHex) + // Disperser configs: rsv window 60s, min chargeable size 100 bytes, price per chargeable 100, global limit 500 + // -> need to check the mock, can't just use any account for the disperser client, consider using static wallets... + + // say with dataLength of 150 bytes, within a window, we can send 7 blobs with overflow of 50 bytes + // the later requests is then 250 bytes, try send 4 blobs within a second, 2 of them would fail but not charged for + // wait for a second, retry, and that should allow ondemand to work + privateKey, err := crypto.HexToECDSA(privateKeyHex[2:]) // Remove "0x" prefix + Expect(err).To(BeNil()) + paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey.D.Bytes())) + disp := clients.NewDisperserClient(&clients.Config{ + Hostname: "localhost", + Port: "32003", + Timeout: 10 * time.Second, + }, signer, clients.NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) + + Expect(disp).To(Not(BeNil())) + + singleBlobSize := uint32(128) + data := make([]byte, singleBlobSize) + _, err = rand.Read(data) + Expect(err).To(BeNil()) + + paddedData := codec.ConvertByPaddingEmptyByte(data) + + // requests that count towards either reservation or payments + paidBlobStatus := []disperser.BlobStatus{} + paidKeys := [][]byte{} + reservationBytesLimit := 1024 + paymentLimit := 512 + // TODO: payment calculation unit consistency + for i := 0; i < (int(reservationBytesLimit+paymentLimit))/int(singleBlobSize); i++ { + blobStatus, key, err := disp.DisperseBlob(ctx, paddedData, []uint8{0}) + Expect(err).To(BeNil()) + Expect(key).To(Not(BeNil())) + Expect(blobStatus).To(Not(BeNil())) + Expect(*blobStatus).To(Equal(disperser.Processing)) + paidBlobStatus = append(paidBlobStatus, *blobStatus) + paidKeys = append(paidKeys, key) + } + + // requests that aren't covered by reservation or on-demand payment + blobStatus, key, err := disp.DispersePaidBlob(ctx, paddedData, []uint8{0}) + Expect(err).To(Not(BeNil())) + Expect(key).To(BeNil()) + Expect(blobStatus).To(BeNil()) + + ticker := time.NewTicker(time.Second * 1) + defer ticker.Stop() + + var replies = make([]*disperserpb.BlobStatusReply, len(paidBlobStatus)) + // now make sure all the paid blobs get confirmed + loop: + for { + select { + case <-ctx.Done(): + Fail("timed out") + case <-ticker.C: + notConfirmed := false + for i, key := range paidKeys { + reply, err := disp.GetBlobStatus(context.Background(), key) + Expect(err).To(BeNil()) + Expect(reply).To(Not(BeNil())) + status, err := disperser.FromBlobStatusProto(reply.GetStatus()) + Expect(err).To(BeNil()) + if *status != disperser.Confirmed { + notConfirmed = true + } + replies[i] = reply + paidBlobStatus[i] = *status + } + + if notConfirmed { + mineAnvilBlocks(numConfirmations + 1) + continue + } + + for _, reply := range replies { + blobHeader := blobHeaderFromProto(reply.GetInfo().GetBlobHeader()) + verificationProof := blobVerificationProofFromProto(reply.GetInfo().GetBlobVerificationProof()) + opts, err := ethClient.GetNoSendTransactOpts() + Expect(err).To(BeNil()) + tx, err := mockRollup.PostCommitment(opts, blobHeader, verificationProof) + Expect(err).To(BeNil()) + tx, err = ethClient.UpdateGas(ctx, tx, nil, gasTipCap, gasFeeCap) + Expect(err).To(BeNil()) + err = ethClient.SendTransaction(ctx, tx) + Expect(err).To(BeNil()) + mineAnvilBlocks(numConfirmations + 1) + _, err = ethClient.EnsureTransactionEvaled(ctx, tx, "PostCommitment") + Expect(err).To(BeNil()) + } + + break loop + } + } + for _, status := range paidBlobStatus { + Expect(status).To(Equal(disperser.Confirmed)) + } + + ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + for _, reply := range replies { + retrieved, err := retrievalClient.RetrieveBlob(ctx, + [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeaderHash()), + reply.GetInfo().GetBlobVerificationProof().GetBlobIndex(), + uint(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber()), + [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), + 0, // retrieve blob 1 from quorum 0 + ) + Expect(err).To(BeNil()) + restored := codec.RemoveEmptyByteFromPaddedBytes(retrieved) + Expect(bytes.TrimRight(restored, "\x00")).To(Equal(bytes.TrimRight(data, "\x00"))) + + _, err = retrievalClient.RetrieveBlob(ctx, + [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeaderHash()), + reply.GetInfo().GetBlobVerificationProof().GetBlobIndex(), + uint(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber()), + [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), + 1, // retrieve blob 1 from quorum 1 + ) + Expect(err).NotTo(BeNil()) + } + }) +}) diff --git a/inabox/tests/ratelimit_test.go b/inabox/tests/ratelimit_test.go index 1f72cbe015..89e270fa71 100644 --- a/inabox/tests/ratelimit_test.go +++ b/inabox/tests/ratelimit_test.go @@ -111,7 +111,7 @@ func testRatelimit(t *testing.T, testConfig *deploy.Config, c ratelimitTestCase) Hostname: "localhost", Port: testConfig.Dispersers[0].DISPERSER_SERVER_GRPC_PORT, Timeout: 10 * time.Second, - }, nil) + }, nil, nil) assert.NoError(t, err) assert.NotNil(t, disp) diff --git a/test/integration_test.go b/test/integration_test.go index 7dde36695b..ffb1a90b71 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -210,18 +210,16 @@ func mustMakeDisperser(t *testing.T, cst core.IndexedChainState, store disperser tx.On("GetCurrentBlockNumber").Return(uint64(100), nil) tx.On("GetQuorumCount").Return(1, nil) - minimumNumSymbols := uint32(128) - pricePerSymbol := uint32(1) - reservationLimit := uint64(1024) - paymentLimit := big.NewInt(512) - // this is disperser client's private key used in tests privateKey, err := crypto.HexToECDSA("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded") // Remove "0x" prefix if err != nil { panic("failed to convert hex to ECDSA") } publicKey := crypto.PubkeyToAddress(privateKey.PublicKey).Hex() + mockState := &coremock.MockOnchainPaymentState{} + reservationLimit := uint64(1024) + paymentLimit := big.NewInt(512) mockState.On("GetActiveReservationByAccount", mock.Anything, mock.MatchedBy(func(account string) bool { return account == publicKey })).Return(core.ActiveReservation{SymbolsPerSec: reservationLimit, StartTimestamp: 0, EndTimestamp: math.MaxUint32, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}}, nil) @@ -232,11 +230,11 @@ func mustMakeDisperser(t *testing.T, cst core.IndexedChainState, store disperser })).Return(core.OnDemandPayment{CumulativePayment: paymentLimit}, nil) mockState.On("GetOnDemandPaymentByAccount", mock.Anything, mock.Anything).Return(core.OnDemandPayment{}, errors.New("payment not found")) mockState.On("GetOnDemandQuorumNumbers", mock.Anything).Return([]uint8{0, 1}, nil) - mockState.On("GetMinNumSymbols", mock.Anything).Return(minimumNumSymbols, nil) - mockState.On("GetPricePerSymbol", mock.Anything).Return(pricePerSymbol, nil) - mockState.On("GetReservationWindow", mock.Anything).Return(uint32(60), nil) mockState.On("GetGlobalSymbolsPerSecond", mock.Anything).Return(uint64(1024), nil) - mockState.On("GetOnDemandQuorumNumbers", mock.Anything).Return([]uint8{0, 1}, nil) + mockState.On("GetPricePerSymbol", mock.Anything).Return(uint32(1), nil) + mockState.On("GetMinNumSymbols", mock.Anything).Return(uint32(128), nil) + mockState.On("GetReservationWindow", mock.Anything).Return(uint32(60), nil) + mockState.On("RefreshOnchainPaymentState", mock.Anything).Return(nil).Maybe() deployLocalStack = !(os.Getenv("DEPLOY_LOCALSTACK") == "false") if !deployLocalStack { From 5c46052042fe619c024b869fedf8df0131807051 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Mon, 28 Oct 2024 18:32:10 -0700 Subject: [PATCH 06/17] feat: blob length converted to symbol --- disperser/apiserver/payment_test.go | 9 +++++---- disperser/apiserver/server.go | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/disperser/apiserver/payment_test.go b/disperser/apiserver/payment_test.go index 8f57e7ea0e..a851782eb7 100644 --- a/disperser/apiserver/payment_test.go +++ b/disperser/apiserver/payment_test.go @@ -11,6 +11,7 @@ import ( "github.com/Layr-Labs/eigenda/core/auth" "github.com/Layr-Labs/eigenda/core/meterer" "github.com/Layr-Labs/eigenda/core/mock" + "github.com/Layr-Labs/eigenda/encoding" "github.com/Layr-Labs/eigenda/encoding/utils/codec" pbcommon "github.com/Layr-Labs/eigenda/api/grpc/common" @@ -37,7 +38,7 @@ func TestDispersePaidBlob(t *testing.T) { dispersalServer := newTestServer(transactor, t.Name()) - data := make([]byte, 1024) + data := make([]byte, 1024*encoding.BYTES_PER_SYMBOL) _, err := rand.Read(data) assert.NoError(t, err) @@ -56,13 +57,13 @@ func TestDispersePaidBlob(t *testing.T) { pk := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeb" signer := auth.NewPaymentSigner(pk) - dataLength := len(data) + symbolLength := encoding.GetBlobLength(uint(len(data))) // disperse on-demand payment for i := 1; i < 3; i++ { pm := pbcommon.PaymentHeader{ AccountId: signer.GetAccountID(), BinIndex: 0, - CumulativePayment: big.NewInt(int64(dataLength * i)).Bytes(), + CumulativePayment: big.NewInt(int64(int(symbolLength) * i)).Bytes(), } sig, err := signer.SignBlobPayment(&pm) assert.NoError(t, err) @@ -81,7 +82,7 @@ func TestDispersePaidBlob(t *testing.T) { pm := pbcommon.PaymentHeader{ AccountId: signer.GetAccountID(), BinIndex: 0, - CumulativePayment: big.NewInt(int64(dataLength*3) - 1).Bytes(), + CumulativePayment: big.NewInt(int64(symbolLength*3) - 1).Bytes(), } sig, err := signer.SignBlobPayment(&pm) assert.NoError(t, err) diff --git a/disperser/apiserver/server.go b/disperser/apiserver/server.go index 8a86280fd2..5f41d22655 100644 --- a/disperser/apiserver/server.go +++ b/disperser/apiserver/server.go @@ -285,7 +285,8 @@ func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, aut // If paymentHeader is not empty, we use the meterer, otherwise we use the ratelimiter if the ratelimiter is available if paymentHeader != nil { - err := s.meterer.MeterRequest(ctx, *paymentHeader, uint(blobSize), blob.GetQuorumNumbers()) + blobLength := encoding.GetBlobLength(uint(blobSize)) + err := s.meterer.MeterRequest(ctx, *paymentHeader, blobLength, blob.GetQuorumNumbers()) if err != nil { return nil, api.NewErrorResourceExhausted(err.Error()) } From 143086d6b9c4fa79fd2f54ba2f1ebc65d12a386c Mon Sep 17 00:00:00 2001 From: hopeyen Date: Mon, 28 Oct 2024 18:47:53 -0700 Subject: [PATCH 07/17] fix: bin record tracking with modularized bin index --- api/clients/accountant.go | 83 +++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index cd3484bd39..b4c6afb767 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -26,7 +26,7 @@ type Accountant struct { // local accounting // contains 3 bins; index 0 for current bin, 1 for next bin, 2 for overflowed bin - binUsages []uint64 + binRecords []BinRecord usageLock sync.Mutex cumulativePayment *big.Int stopRotation chan struct{} @@ -34,6 +34,11 @@ type Accountant struct { paymentSigner core.PaymentSigner } +type BinRecord struct { + Index uint32 + Usage uint64 +} + func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPayment, reservationWindow uint32, pricePerSymbol uint32, minNumSymbols uint32, paymentSigner core.PaymentSigner) *Accountant { //TODO: client storage; currently every instance starts fresh but on-chain or a small store makes more sense // Also client is currently responsible for supplying network params, we need to add RPC in order to be automatic @@ -44,68 +49,42 @@ func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPay reservationWindow: reservationWindow, pricePerSymbol: pricePerSymbol, minNumSymbols: minNumSymbols, - binUsages: []uint64{0, 0, 0}, + binRecords: []BinRecord{{Index: 0, Usage: 0}, {Index: 1, Usage: 0}, {Index: 2, Usage: 0}}, cumulativePayment: big.NewInt(0), stopRotation: make(chan struct{}), paymentSigner: paymentSigner, } - go a.startBinRotation() // TODO: add a routine to refresh the on-chain state occasionally? return &a } -func (a *Accountant) startBinRotation() { - ticker := time.NewTicker(time.Duration(a.reservationWindow) * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - a.rotateBins() - case <-a.stopRotation: - return - } - } -} - -func (a *Accountant) rotateBins() { - a.usageLock.Lock() - defer a.usageLock.Unlock() - // Shift bins: bin_i to bin_{i-1}, set 0 to bin2 - a.binUsages[0] = a.binUsages[1] - a.binUsages[1] = a.binUsages[2] - a.binUsages[2] = 0 -} - -func (a *Accountant) Stop() { - close(a.stopRotation) -} - // accountant calculates and records payment information func (a *Accountant) BlobPaymentInfo(ctx context.Context, dataLength uint64) (uint32, *big.Int, error) { - //TODO: do we need to lock the binUsages here in case the blob rotation happens in the middle of the function? - // binUsage := a.binUsages[0] + dataLength - a.usageLock.Lock() - defer a.usageLock.Unlock() - a.binUsages[0] += dataLength now := time.Now().Unix() currentBinIndex := meterer.GetBinIndex(uint64(now), a.reservationWindow) + // index := time.Now().Unix() / int64(a.reservationWindow) + + a.usageLock.Lock() + defer a.usageLock.Unlock() + relativeBinRecord := a.GetRelativeBinRecord(currentBinIndex) + relativeBinRecord.Usage += dataLength // first attempt to use the active reservation binLimit := a.reservation.SymbolsPerSec * uint64(a.reservationWindow) - if a.binUsages[0] <= binLimit { + if relativeBinRecord.Usage <= binLimit { return currentBinIndex, big.NewInt(0), nil } + overflowBinRecord := a.GetOverflowBinRecord(currentBinIndex) // Allow one overflow when the overflow bin is empty, the current usage and new length are both less than the limit - if a.binUsages[2] == 0 && a.binUsages[0]-dataLength < binLimit && dataLength <= binLimit { - a.binUsages[2] += a.binUsages[0] - binLimit + if overflowBinRecord.Usage == 0 && relativeBinRecord.Usage-dataLength < binLimit && dataLength <= binLimit { + overflowBinRecord.Usage += relativeBinRecord.Usage - binLimit return currentBinIndex, big.NewInt(0), nil } // reservation not available, attempt on-demand //todo: rollback if disperser respond with some type of rejection? - a.binUsages[0] -= dataLength + relativeBinRecord.Usage -= dataLength incrementRequired := big.NewInt(int64(a.PaymentCharged(uint(dataLength)))) a.cumulativePayment.Add(a.cumulativePayment, incrementRequired) if a.cumulativePayment.Cmp(a.onDemand.CumulativePayment) <= 0 { @@ -152,3 +131,29 @@ func (a *Accountant) SymbolsCharged(dataLength uint) uint32 { // Round up to the nearest multiple of MinNumSymbols return uint32(core.RoundUpDivide(uint(dataLength), uint(a.minNumSymbols))) * a.minNumSymbols } + +func (a *Accountant) GetRelativeBinRecord(index uint32) BinRecord { + relativeIndex := index % 3 + + if a.binRecords[relativeIndex].Index != uint32(index) { + a.binRecords[relativeIndex] = BinRecord{ + Index: uint32(index), + Usage: 0, + } + } + + return a.binRecords[relativeIndex] +} + +func (a *Accountant) GetOverflowBinRecord(index uint32) BinRecord { + relativeIndex := (index + 2) % 3 + + if a.binRecords[relativeIndex].Index != uint32(index+2) { + a.binRecords[relativeIndex] = BinRecord{ + Index: uint32(index + 2), + Usage: 0, + } + } + + return a.binRecords[relativeIndex] +} From 89522729fb54cb9b5cb96ad18a89ee1829daa3ef Mon Sep 17 00:00:00 2001 From: hopeyen Date: Mon, 28 Oct 2024 19:09:05 -0700 Subject: [PATCH 08/17] refactor: symbolLength consistency --- api/clients/accountant.go | 3 +- core/meterer/meterer.go | 46 +++++++++++++-------------- core/meterer/meterer_test.go | 48 ++++++++++++++--------------- disperser/apiserver/payment_test.go | 6 ++-- disperser/apiserver/server_test.go | 2 +- inabox/tests/payment_test.go | 9 +++--- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index b4c6afb767..1f1bc3e0be 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -25,7 +25,7 @@ type Accountant struct { minNumSymbols uint32 // local accounting - // contains 3 bins; index 0 for current bin, 1 for next bin, 2 for overflowed bin + // contains 3 bins; circular wrapping of indices binRecords []BinRecord usageLock sync.Mutex cumulativePayment *big.Int @@ -62,7 +62,6 @@ func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPay func (a *Accountant) BlobPaymentInfo(ctx context.Context, dataLength uint64) (uint32, *big.Int, error) { now := time.Now().Unix() currentBinIndex := meterer.GetBinIndex(uint64(now), a.reservationWindow) - // index := time.Now().Unix() / int64(a.reservationWindow) a.usageLock.Lock() defer a.usageLock.Unlock() diff --git a/core/meterer/meterer.go b/core/meterer/meterer.go index 964205f33d..ba1f2cfc65 100644 --- a/core/meterer/meterer.go +++ b/core/meterer/meterer.go @@ -70,14 +70,14 @@ func (m *Meterer) Start(ctx context.Context) { // MeterRequest validates a blob header and adds it to the meterer's state // TODO: return error if there's a rejection (with reasoning) or internal error (should be very rare) -func (m *Meterer) MeterRequest(ctx context.Context, header core.PaymentMetadata, blobLength uint, quorumNumbers []uint8) error { +func (m *Meterer) MeterRequest(ctx context.Context, header core.PaymentMetadata, numSymbols uint, quorumNumbers []uint8) error { // Validate against the payment method if header.CumulativePayment.Sign() == 0 { reservation, err := m.ChainPaymentState.GetActiveReservationByAccount(ctx, header.AccountID) if err != nil { return fmt.Errorf("failed to get active reservation by account: %w", err) } - if err := m.ServeReservationRequest(ctx, header, &reservation, blobLength, quorumNumbers); err != nil { + if err := m.ServeReservationRequest(ctx, header, &reservation, numSymbols, quorumNumbers); err != nil { return fmt.Errorf("invalid reservation: %w", err) } } else { @@ -85,7 +85,7 @@ func (m *Meterer) MeterRequest(ctx context.Context, header core.PaymentMetadata, if err != nil { return fmt.Errorf("failed to get on-demand payment by account: %w", err) } - if err := m.ServeOnDemandRequest(ctx, header, &onDemandPayment, blobLength, quorumNumbers); err != nil { + if err := m.ServeOnDemandRequest(ctx, header, &onDemandPayment, numSymbols, quorumNumbers); err != nil { return fmt.Errorf("invalid on-demand request: %w", err) } } @@ -94,7 +94,7 @@ func (m *Meterer) MeterRequest(ctx context.Context, header core.PaymentMetadata, } // ServeReservationRequest handles the rate limiting logic for incoming requests -func (m *Meterer) ServeReservationRequest(ctx context.Context, header core.PaymentMetadata, reservation *core.ActiveReservation, blobLength uint, quorumNumbers []uint8) error { +func (m *Meterer) ServeReservationRequest(ctx context.Context, header core.PaymentMetadata, reservation *core.ActiveReservation, numSymbols uint, quorumNumbers []uint8) error { if err := m.ValidateQuorum(quorumNumbers, reservation.QuorumNumbers); err != nil { return fmt.Errorf("invalid quorum for reservation: %w", err) } @@ -103,7 +103,7 @@ func (m *Meterer) ServeReservationRequest(ctx context.Context, header core.Payme } // Update bin usage atomically and check against reservation's data rate as the bin limit - if err := m.IncrementBinUsage(ctx, header, reservation, blobLength); err != nil { + if err := m.IncrementBinUsage(ctx, header, reservation, numSymbols); err != nil { return fmt.Errorf("bin overflows: %w", err) } @@ -142,9 +142,9 @@ func (m *Meterer) ValidateBinIndex(header core.PaymentMetadata, reservation *cor } // IncrementBinUsage increments the bin usage atomically and checks for overflow -func (m *Meterer) IncrementBinUsage(ctx context.Context, header core.PaymentMetadata, reservation *core.ActiveReservation, blobLength uint) error { - numSymbols := m.SymbolsCharged(blobLength) - newUsage, err := m.OffchainStore.UpdateReservationBin(ctx, header.AccountID, uint64(header.BinIndex), uint64(numSymbols)) +func (m *Meterer) IncrementBinUsage(ctx context.Context, header core.PaymentMetadata, reservation *core.ActiveReservation, numSymbols uint) error { + symbolsCharged := m.SymbolsCharged(numSymbols) + newUsage, err := m.OffchainStore.UpdateReservationBin(ctx, header.AccountID, uint64(header.BinIndex), uint64(symbolsCharged)) if err != nil { return fmt.Errorf("failed to increment bin usage: %w", err) } @@ -176,7 +176,7 @@ func GetBinIndex(timestamp uint64, binInterval uint32) uint32 { // ServeOnDemandRequest handles the rate limiting logic for incoming requests // On-demand requests doesn't have additional quorum settings and should only be // allowed by ETH and EIGEN quorums -func (m *Meterer) ServeOnDemandRequest(ctx context.Context, header core.PaymentMetadata, onDemandPayment *core.OnDemandPayment, blobLength uint, headerQuorums []uint8) error { +func (m *Meterer) ServeOnDemandRequest(ctx context.Context, header core.PaymentMetadata, onDemandPayment *core.OnDemandPayment, numSymbols uint, headerQuorums []uint8) error { quorumNumbers, err := m.ChainPaymentState.GetOnDemandQuorumNumbers(ctx) if err != nil { return fmt.Errorf("failed to get on-demand quorum numbers: %w", err) @@ -186,13 +186,13 @@ func (m *Meterer) ServeOnDemandRequest(ctx context.Context, header core.PaymentM return fmt.Errorf("invalid quorum for On-Demand Request: %w", err) } // update blob header to use the miniumum chargeable size - symbolsCharged := m.SymbolsCharged(blobLength) + symbolsCharged := m.SymbolsCharged(numSymbols) err = m.OffchainStore.AddOnDemandPayment(ctx, header, symbolsCharged) if err != nil { return fmt.Errorf("failed to update cumulative payment: %w", err) } // Validate payments attached - err = m.ValidatePayment(ctx, header, onDemandPayment, blobLength) + err = m.ValidatePayment(ctx, header, onDemandPayment, numSymbols) if err != nil { // No tolerance for incorrect payment amounts; no rollbacks return fmt.Errorf("invalid on-demand payment: %w", err) @@ -214,25 +214,25 @@ func (m *Meterer) ServeOnDemandRequest(ctx context.Context, header core.PaymentM // ValidatePayment checks if the provided payment header is valid against the local accounting // prevPmt is the largest cumulative payment strictly less than PaymentMetadata.cumulativePayment if exists // nextPmt is the smallest cumulative payment strictly greater than PaymentMetadata.cumulativePayment if exists -// nextPmtDataLength is the dataLength of corresponding to nextPmt if exists -// prevPmt + PaymentMetadata.DataLength * m.FixedFeePerByte +// nextPmtnumSymbols is the numSymbols of corresponding to nextPmt if exists +// prevPmt + PaymentMetadata.numSymbols * m.FixedFeePerByte // <= PaymentMetadata.CumulativePayment -// <= nextPmt - nextPmtDataLength * m.FixedFeePerByte > nextPmt -func (m *Meterer) ValidatePayment(ctx context.Context, header core.PaymentMetadata, onDemandPayment *core.OnDemandPayment, blobLength uint) error { +// <= nextPmt - nextPmtnumSymbols * m.FixedFeePerByte > nextPmt +func (m *Meterer) ValidatePayment(ctx context.Context, header core.PaymentMetadata, onDemandPayment *core.OnDemandPayment, numSymbols uint) error { if header.CumulativePayment.Cmp(onDemandPayment.CumulativePayment) > 0 { return fmt.Errorf("request claims a cumulative payment greater than the on-chain deposit") } - prevPmt, nextPmt, nextPmtDataLength, err := m.OffchainStore.GetRelevantOnDemandRecords(ctx, header.AccountID, header.CumulativePayment) // zero if DNE + prevPmt, nextPmt, nextPmtnumSymbols, err := m.OffchainStore.GetRelevantOnDemandRecords(ctx, header.AccountID, header.CumulativePayment) // zero if DNE if err != nil { return fmt.Errorf("failed to get relevant on-demand records: %w", err) } // the current request must increment cumulative payment by a magnitude sufficient to cover the blob size - if prevPmt+m.PaymentCharged(blobLength) > header.CumulativePayment.Uint64() { + if prevPmt+m.PaymentCharged(numSymbols) > header.CumulativePayment.Uint64() { return fmt.Errorf("insufficient cumulative payment increment") } // the current request must not break the payment magnitude for the next payment if the two requests were delivered out-of-order - if nextPmt != 0 && header.CumulativePayment.Uint64()+m.PaymentCharged(uint(nextPmtDataLength)) > nextPmt { + if nextPmt != 0 && header.CumulativePayment.Uint64()+m.PaymentCharged(uint(nextPmtnumSymbols)) > nextPmt { return fmt.Errorf("breaking cumulative payment invariants") } // check passed: blob can be safely inserted into the set of payments @@ -240,18 +240,18 @@ func (m *Meterer) ValidatePayment(ctx context.Context, header core.PaymentMetada } // PaymentCharged returns the chargeable price for a given data length -func (m *Meterer) PaymentCharged(dataLength uint) uint64 { - return uint64(m.SymbolsCharged(dataLength)) * uint64(m.ChainPaymentState.GetPricePerSymbol()) +func (m *Meterer) PaymentCharged(numSymbols uint) uint64 { + return uint64(m.SymbolsCharged(numSymbols)) * uint64(m.ChainPaymentState.GetPricePerSymbol()) } // SymbolsCharged returns the number of symbols charged for a given data length // being at least MinNumSymbols or the nearest rounded-up multiple of MinNumSymbols. -func (m *Meterer) SymbolsCharged(dataLength uint) uint32 { - if dataLength <= uint(m.ChainPaymentState.GetMinNumSymbols()) { +func (m *Meterer) SymbolsCharged(numSymbols uint) uint32 { + if numSymbols <= uint(m.ChainPaymentState.GetMinNumSymbols()) { return m.ChainPaymentState.GetMinNumSymbols() } // Round up to the nearest multiple of MinNumSymbols - return uint32(core.RoundUpDivide(uint(dataLength), uint(m.ChainPaymentState.GetMinNumSymbols()))) * m.ChainPaymentState.GetMinNumSymbols() + return uint32(core.RoundUpDivide(uint(numSymbols), uint(m.ChainPaymentState.GetMinNumSymbols()))) * m.ChainPaymentState.GetMinNumSymbols() } // ValidateBinIndex checks if the provided bin index is valid diff --git a/core/meterer/meterer_test.go b/core/meterer/meterer_test.go index fbab39bc3c..aa2537ef34 100644 --- a/core/meterer/meterer_test.go +++ b/core/meterer/meterer_test.go @@ -213,11 +213,11 @@ func TestMetererReservations(t *testing.T) { assert.ErrorContains(t, err, "invalid bin index for reservation") // test bin usage metering - dataLength := uint(20) + symbolLength := uint(20) requiredLength := uint(21) // 21 should be charged for length of 20 since minNumSymbols is 3 for i := 0; i < 9; i++ { header = createPaymentHeader(binIndex, 0, accountID2) - err = mt.MeterRequest(ctx, *header, dataLength, quoromNumbers) + err = mt.MeterRequest(ctx, *header, symbolLength, quoromNumbers) assert.NoError(t, err) item, err := dynamoClient.GetItem(ctx, reservationTableName, commondynamodb.Key{ "AccountID": &types.AttributeValueMemberS{Value: accountID2}, @@ -296,20 +296,20 @@ func TestMetererOnDemand(t *testing.T) { assert.Equal(t, 1, len(result)) // test duplicated cumulative payments - dataLength := uint(100) - priceCharged := mt.PaymentCharged(dataLength) + symbolLength := uint(100) + priceCharged := mt.PaymentCharged(symbolLength) assert.Equal(t, uint64(102*mt.ChainPaymentState.GetPricePerSymbol()), priceCharged) header = createPaymentHeader(binIndex, priceCharged, accountID2) - err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) + err = mt.MeterRequest(ctx, *header, symbolLength, quorumNumbers) assert.NoError(t, err) header = createPaymentHeader(binIndex, priceCharged, accountID2) - err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) + err = mt.MeterRequest(ctx, *header, symbolLength, quorumNumbers) assert.ErrorContains(t, err, "exact payment already exists") // test valid payments for i := 1; i < 9; i++ { header = createPaymentHeader(binIndex, uint64(priceCharged)*uint64(i+1), accountID2) - err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) + err = mt.MeterRequest(ctx, *header, symbolLength, quorumNumbers) assert.NoError(t, err) } @@ -320,10 +320,10 @@ func TestMetererOnDemand(t *testing.T) { // test insufficient increment in cumulative payment previousCumulativePayment := uint64(priceCharged) * uint64(9) - dataLength = uint(2) - priceCharged = mt.PaymentCharged(dataLength) + symbolLength = uint(2) + priceCharged = mt.PaymentCharged(symbolLength) header = createPaymentHeader(binIndex, previousCumulativePayment+priceCharged-1, accountID2) - err = mt.MeterRequest(ctx, *header, dataLength, quorumNumbers) + err = mt.MeterRequest(ctx, *header, symbolLength, quorumNumbers) assert.ErrorContains(t, err, "invalid on-demand payment: insufficient cumulative payment increment") previousCumulativePayment = previousCumulativePayment + priceCharged @@ -355,42 +355,42 @@ func TestMetererOnDemand(t *testing.T) { func TestMeterer_paymentCharged(t *testing.T) { tests := []struct { name string - dataLength uint + symbolLength uint pricePerSymbol uint32 minNumSymbols uint32 expected uint64 }{ { name: "Data length equal to min chargeable size", - dataLength: 1024, + symbolLength: 1024, pricePerSymbol: 1, minNumSymbols: 1024, expected: 1024, }, { name: "Data length less than min chargeable size", - dataLength: 512, + symbolLength: 512, pricePerSymbol: 1, minNumSymbols: 1024, expected: 1024, }, { name: "Data length greater than min chargeable size", - dataLength: 2048, + symbolLength: 2048, pricePerSymbol: 1, minNumSymbols: 1024, expected: 2048, }, { name: "Large data length", - dataLength: 1 << 20, // 1 MB + symbolLength: 1 << 20, // 1 MB pricePerSymbol: 1, minNumSymbols: 1024, expected: 1 << 20, }, { name: "Price not evenly divisible by min chargeable size", - dataLength: 1536, + symbolLength: 1536, pricePerSymbol: 1, minNumSymbols: 1024, expected: 2048, @@ -405,7 +405,7 @@ func TestMeterer_paymentCharged(t *testing.T) { m := &meterer.Meterer{ ChainPaymentState: paymentChainState, } - result := m.PaymentCharged(tt.dataLength) + result := m.PaymentCharged(tt.symbolLength) assert.Equal(t, tt.expected, result) }) } @@ -414,37 +414,37 @@ func TestMeterer_paymentCharged(t *testing.T) { func TestMeterer_symbolsCharged(t *testing.T) { tests := []struct { name string - dataLength uint + symbolLength uint minNumSymbols uint32 expected uint32 }{ { name: "Data length equal to min number of symobols", - dataLength: 1024, + symbolLength: 1024, minNumSymbols: 1024, expected: 1024, }, { name: "Data length less than min number of symbols", - dataLength: 512, + symbolLength: 512, minNumSymbols: 1024, expected: 1024, }, { name: "Data length greater than min number of symbols", - dataLength: 2048, + symbolLength: 2048, minNumSymbols: 1024, expected: 2048, }, { name: "Large data length", - dataLength: 1 << 20, // 1 MB + symbolLength: 1 << 20, // 1 MB minNumSymbols: 1024, expected: 1 << 20, }, { name: "Very small data length", - dataLength: 16, + symbolLength: 16, minNumSymbols: 1024, expected: 1024, }, @@ -457,7 +457,7 @@ func TestMeterer_symbolsCharged(t *testing.T) { m := &meterer.Meterer{ ChainPaymentState: paymentChainState, } - result := m.SymbolsCharged(tt.dataLength) + result := m.SymbolsCharged(tt.symbolLength) assert.Equal(t, tt.expected, result) }) } diff --git a/disperser/apiserver/payment_test.go b/disperser/apiserver/payment_test.go index a851782eb7..23ac774eda 100644 --- a/disperser/apiserver/payment_test.go +++ b/disperser/apiserver/payment_test.go @@ -38,7 +38,7 @@ func TestDispersePaidBlob(t *testing.T) { dispersalServer := newTestServer(transactor, t.Name()) - data := make([]byte, 1024*encoding.BYTES_PER_SYMBOL) + data := make([]byte, 1024) _, err := rand.Read(data) assert.NoError(t, err) @@ -63,7 +63,7 @@ func TestDispersePaidBlob(t *testing.T) { pm := pbcommon.PaymentHeader{ AccountId: signer.GetAccountID(), BinIndex: 0, - CumulativePayment: big.NewInt(int64(int(symbolLength) * i)).Bytes(), + CumulativePayment: big.NewInt(int64(int(symbolLength) * i * encoding.BYTES_PER_SYMBOL)).Bytes(), } sig, err := signer.SignBlobPayment(&pm) assert.NoError(t, err) @@ -82,7 +82,7 @@ func TestDispersePaidBlob(t *testing.T) { pm := pbcommon.PaymentHeader{ AccountId: signer.GetAccountID(), BinIndex: 0, - CumulativePayment: big.NewInt(int64(symbolLength*3) - 1).Bytes(), + CumulativePayment: big.NewInt(int64(symbolLength*3)*encoding.BYTES_PER_SYMBOL - 1).Bytes(), } sig, err := signer.SignBlobPayment(&pm) assert.NoError(t, err) diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index 5cfcb448f5..18e4321ae8 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -703,7 +703,7 @@ func newTestServer(transactor core.Writer, testName string) *apiserver.Dispersal panic("failed to make initial query to the on-chain state") } - mockState.On("GetPricePerSymbol").Return(uint32(1), nil) + mockState.On("GetPricePerSymbol").Return(uint32(encoding.BYTES_PER_SYMBOL), nil) mockState.On("GetMinNumSymbols").Return(uint32(1), nil) mockState.On("GetGlobalSymbolsPerSecond").Return(uint64(4096), nil) mockState.On("GetRequiredQuorumNumbers").Return([]uint8{0, 1}, nil) diff --git a/inabox/tests/payment_test.go b/inabox/tests/payment_test.go index 6da9c40c78..73def60e13 100644 --- a/inabox/tests/payment_test.go +++ b/inabox/tests/payment_test.go @@ -12,6 +12,7 @@ import ( "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" "github.com/Layr-Labs/eigenda/disperser" + "github.com/Layr-Labs/eigenda/encoding" "github.com/ethereum/go-ethereum/crypto" "github.com/Layr-Labs/eigenda/encoding/utils/codec" @@ -46,8 +47,8 @@ var _ = Describe("Inabox Integration", func() { Expect(disp).To(Not(BeNil())) - singleBlobSize := uint32(128) - data := make([]byte, singleBlobSize) + blobLength := uint32(4) + data := make([]byte, blobLength*encoding.BYTES_PER_SYMBOL) _, err = rand.Read(data) Expect(err).To(BeNil()) @@ -59,8 +60,8 @@ var _ = Describe("Inabox Integration", func() { reservationBytesLimit := 1024 paymentLimit := 512 // TODO: payment calculation unit consistency - for i := 0; i < (int(reservationBytesLimit+paymentLimit))/int(singleBlobSize); i++ { - blobStatus, key, err := disp.DisperseBlob(ctx, paddedData, []uint8{0}) + for i := 0; i < (int(reservationBytesLimit+paymentLimit))/int(blobLength); i++ { + blobStatus, key, err := disp.DispersePaidBlob(ctx, paddedData, []uint8{0}) Expect(err).To(BeNil()) Expect(key).To(Not(BeNil())) Expect(blobStatus).To(Not(BeNil())) From 150733d4a633e5b020a550a3f4d76bd4afca3038 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Wed, 30 Oct 2024 12:07:49 -0700 Subject: [PATCH 09/17] refactor: update tests and add quorum check --- api/clients/accountant.go | 63 +++++---- api/clients/accountant_test.go | 236 ++++++++++++++++++-------------- api/clients/disperser_client.go | 2 +- 3 files changed, 170 insertions(+), 131 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index 1f1bc3e0be..089b673b2b 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "slices" "sync" "time" @@ -59,42 +60,51 @@ func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPay } // accountant calculates and records payment information -func (a *Accountant) BlobPaymentInfo(ctx context.Context, dataLength uint64) (uint32, *big.Int, error) { +func (a *Accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quorumNumbers []uint8) (uint32, *big.Int, error) { now := time.Now().Unix() currentBinIndex := meterer.GetBinIndex(uint64(now), a.reservationWindow) a.usageLock.Lock() defer a.usageLock.Unlock() relativeBinRecord := a.GetRelativeBinRecord(currentBinIndex) - relativeBinRecord.Usage += dataLength + relativeBinRecord.Usage += numSymbols // first attempt to use the active reservation binLimit := a.reservation.SymbolsPerSec * uint64(a.reservationWindow) if relativeBinRecord.Usage <= binLimit { + if err := QuorumCheck(quorumNumbers, a.reservation.QuorumNumbers); err != nil { + return 0, big.NewInt(0), err + } return currentBinIndex, big.NewInt(0), nil } - overflowBinRecord := a.GetOverflowBinRecord(currentBinIndex) + overflowBinRecord := a.GetRelativeBinRecord(currentBinIndex + 2) // Allow one overflow when the overflow bin is empty, the current usage and new length are both less than the limit - if overflowBinRecord.Usage == 0 && relativeBinRecord.Usage-dataLength < binLimit && dataLength <= binLimit { + if overflowBinRecord.Usage == 0 && relativeBinRecord.Usage-numSymbols < binLimit && numSymbols <= binLimit { overflowBinRecord.Usage += relativeBinRecord.Usage - binLimit + if err := QuorumCheck(quorumNumbers, a.reservation.QuorumNumbers); err != nil { + return 0, big.NewInt(0), err + } return currentBinIndex, big.NewInt(0), nil } // reservation not available, attempt on-demand - //todo: rollback if disperser respond with some type of rejection? - relativeBinRecord.Usage -= dataLength - incrementRequired := big.NewInt(int64(a.PaymentCharged(uint(dataLength)))) + //todo: rollback later if disperser respond with some type of rejection? + relativeBinRecord.Usage -= numSymbols + incrementRequired := big.NewInt(int64(a.PaymentCharged(uint(numSymbols)))) a.cumulativePayment.Add(a.cumulativePayment, incrementRequired) if a.cumulativePayment.Cmp(a.onDemand.CumulativePayment) <= 0 { + if err := QuorumCheck(quorumNumbers, []uint8{0, 1}); err != nil { + return 0, big.NewInt(0), err + } return 0, a.cumulativePayment, nil } - return 0, big.NewInt(0), fmt.Errorf("Accountant cannot approve payment for this blob") + return 0, big.NewInt(0), fmt.Errorf("neither reservation nor on-demand payment is available") } // accountant provides and records payment information -func (a *Accountant) AccountBlob(ctx context.Context, dataLength uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) { - binIndex, cumulativePayment, err := a.BlobPaymentInfo(ctx, dataLength) +func (a *Accountant) AccountBlob(ctx context.Context, numSymbols uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) { + binIndex, cumulativePayment, err := a.BlobPaymentInfo(ctx, numSymbols, quorums) if err != nil { return nil, nil, err } @@ -117,23 +127,22 @@ func (a *Accountant) AccountBlob(ctx context.Context, dataLength uint64, quorums // TODO: PaymentCharged and SymbolsCharged copied from meterer, should be refactored // PaymentCharged returns the chargeable price for a given data length -func (a *Accountant) PaymentCharged(dataLength uint) uint64 { - return uint64(a.SymbolsCharged(dataLength)) * uint64(a.pricePerSymbol) +func (a *Accountant) PaymentCharged(numSymbols uint) uint64 { + return uint64(a.SymbolsCharged(numSymbols)) * uint64(a.pricePerSymbol) } // SymbolsCharged returns the number of symbols charged for a given data length // being at least MinNumSymbols or the nearest rounded-up multiple of MinNumSymbols. -func (a *Accountant) SymbolsCharged(dataLength uint) uint32 { - if dataLength <= uint(a.minNumSymbols) { +func (a *Accountant) SymbolsCharged(numSymbols uint) uint32 { + if numSymbols <= uint(a.minNumSymbols) { return a.minNumSymbols } // Round up to the nearest multiple of MinNumSymbols - return uint32(core.RoundUpDivide(uint(dataLength), uint(a.minNumSymbols))) * a.minNumSymbols + return uint32(core.RoundUpDivide(uint(numSymbols), uint(a.minNumSymbols))) * a.minNumSymbols } -func (a *Accountant) GetRelativeBinRecord(index uint32) BinRecord { +func (a *Accountant) GetRelativeBinRecord(index uint32) *BinRecord { relativeIndex := index % 3 - if a.binRecords[relativeIndex].Index != uint32(index) { a.binRecords[relativeIndex] = BinRecord{ Index: uint32(index), @@ -141,18 +150,18 @@ func (a *Accountant) GetRelativeBinRecord(index uint32) BinRecord { } } - return a.binRecords[relativeIndex] + return &a.binRecords[relativeIndex] } -func (a *Accountant) GetOverflowBinRecord(index uint32) BinRecord { - relativeIndex := (index + 2) % 3 - - if a.binRecords[relativeIndex].Index != uint32(index+2) { - a.binRecords[relativeIndex] = BinRecord{ - Index: uint32(index + 2), - Usage: 0, +// QuorumCheck eagerly returns error if the check finds a quorum number not an element of the allowed quorum numbers +func QuorumCheck(quorumNumbers []uint8, allowedNumbers []uint8) error { + if len(quorumNumbers) == 0 { + return fmt.Errorf("no quorum numbers provided") + } + for _, quorum := range quorumNumbers { + if !slices.Contains(allowedNumbers, quorum) { + return fmt.Errorf("provided quorum number %v not allowed", quorum) } } - - return a.binRecords[relativeIndex] + return nil } diff --git a/api/clients/accountant_test.go b/api/clients/accountant_test.go index 84c44fda9b..443f5746df 100644 --- a/api/clients/accountant_test.go +++ b/api/clients/accountant_test.go @@ -3,6 +3,7 @@ package clients import ( "context" "encoding/hex" + "fmt" "math/big" "sync" "testing" @@ -41,7 +42,7 @@ func TestNewAccountant(t *testing.T) { assert.Equal(t, reservationWindow, accountant.reservationWindow) assert.Equal(t, pricePerSymbol, accountant.pricePerSymbol) assert.Equal(t, minNumSymbols, accountant.minNumSymbols) - assert.Equal(t, []uint64{0, 0, 0}, accountant.binUsages) + assert.Equal(t, []BinRecord{{Index: 0, Usage: 0}, {Index: 1, Usage: 0}, {Index: 2, Usage: 0}}, accountant.binRecords) assert.Equal(t, big.NewInt(0), accountant.cumulativePayment) } @@ -64,29 +65,28 @@ func TestAccountBlob_Reservation(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() - dataLength := uint64(500) + symbolLength := uint64(500) quorums := []uint8{0, 1} - header, _, err := accountant.AccountBlob(ctx, dataLength, quorums) + header, _, err := accountant.AccountBlob(ctx, symbolLength, quorums) metadata := core.ConvertPaymentHeader(header) assert.NoError(t, err) assert.Equal(t, meterer.GetBinIndex(uint64(time.Now().Unix()), reservationWindow), header.BinIndex) assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) - assert.Equal(t, []uint64{500, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{500, 0, 0}, mapRecordUsage(accountant.binRecords)), true) - dataLength = uint64(700) + symbolLength = uint64(700) - header, _, err = accountant.AccountBlob(ctx, dataLength, quorums) + header, _, err = accountant.AccountBlob(ctx, symbolLength, quorums) metadata = core.ConvertPaymentHeader(header) assert.NoError(t, err) assert.NotEqual(t, 0, header.BinIndex) assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) - assert.Equal(t, []uint64{1200, 0, 200}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{1200, 0, 200}, mapRecordUsage(accountant.binRecords)), true) // Second call should use on-demand payment header, _, err = accountant.AccountBlob(ctx, 300, quorums) @@ -94,7 +94,7 @@ func TestAccountBlob_Reservation(t *testing.T) { assert.NoError(t, err) assert.Equal(t, uint32(0), header.BinIndex) - assert.Equal(t, big.NewInt(3), metadata.CumulativePayment) + assert.Equal(t, big.NewInt(300), metadata.CumulativePayment) } func TestAccountBlob_OnDemand(t *testing.T) { @@ -106,7 +106,7 @@ func TestAccountBlob_OnDemand(t *testing.T) { QuorumNumbers: []uint8{0, 1}, } onDemand := core.OnDemandPayment{ - CumulativePayment: big.NewInt(500), + CumulativePayment: big.NewInt(1500), } reservationWindow := uint32(5) pricePerSymbol := uint32(1) @@ -116,19 +116,19 @@ func TestAccountBlob_OnDemand(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() - dataLength := uint64(1500) + numSymbols := uint64(1500) quorums := []uint8{0, 1} - header, _, err := accountant.AccountBlob(ctx, dataLength, quorums) - metadata := core.ConvertPaymentHeader(header) - expectedPayment := big.NewInt(int64(dataLength * uint64(pricePerSymbol) / uint64(minNumSymbols))) + header, _, err := accountant.AccountBlob(ctx, numSymbols, quorums) assert.NoError(t, err) + + metadata := core.ConvertPaymentHeader(header) + expectedPayment := big.NewInt(int64(numSymbols * uint64(pricePerSymbol))) assert.Equal(t, uint32(0), header.BinIndex) assert.Equal(t, expectedPayment, metadata.CumulativePayment) - assert.Equal(t, []uint64{0, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{0, 0, 0}, mapRecordUsage(accountant.binRecords)), true) assert.Equal(t, expectedPayment, accountant.cumulativePayment) } @@ -145,16 +145,13 @@ func TestAccountBlob_InsufficientOnDemand(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() - dataLength := uint64(2000) + numSymbols := uint64(2000) quorums := []uint8{0, 1} - _, _, err = accountant.AccountBlob(ctx, dataLength, quorums) - - assert.Error(t, err) - assert.Contains(t, err.Error(), "Accountant cannot approve payment for this blob") + _, _, err = accountant.AccountBlob(ctx, numSymbols, quorums) + assert.Contains(t, err.Error(), "neither reservation nor on-demand payment is available") } func TestAccountBlobCallSeries(t *testing.T) { @@ -169,14 +166,13 @@ func TestAccountBlobCallSeries(t *testing.T) { CumulativePayment: big.NewInt(1000), } reservationWindow := uint32(5) - pricePerSymbol := uint32(100) + pricePerSymbol := uint32(1) minNumSymbols := uint32(100) privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() quorums := []uint8{0, 1} @@ -206,7 +202,7 @@ func TestAccountBlobCallSeries(t *testing.T) { // Fourth call: Insufficient on-demand _, _, err = accountant.AccountBlob(ctx, 600, quorums) assert.Error(t, err) - assert.Contains(t, err.Error(), "Accountant cannot approve payment for this blob") + assert.Contains(t, err.Error(), "neither reservation nor on-demand payment is available") } func TestAccountBlob_BinRotation(t *testing.T) { @@ -227,77 +223,28 @@ func TestAccountBlob_BinRotation(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() quorums := []uint8{0, 1} // First call - _, _, err = accountant.AccountBlob(ctx, 800, quorums) + header, _, err := accountant.AccountBlob(ctx, 800, quorums) assert.NoError(t, err) - assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{800, 0, 0}, mapRecordUsage(accountant.binRecords)), true) - // Wait for bin rotation - time.Sleep(2 * time.Second) + // next reservation duration + time.Sleep(1000 * time.Millisecond) - // Second call after bin rotation - _, _, err = accountant.AccountBlob(ctx, 300, quorums) + // Second call + header, _, err = accountant.AccountBlob(ctx, 300, quorums) assert.NoError(t, err) - assert.Equal(t, []uint64{300, 0, 0}, accountant.binUsages) + fmt.Println("shifts ", header.BinIndex%3) + assert.Equal(t, isRotation([]uint64{800, 300, 0}, mapRecordUsage(accountant.binRecords)), true) // Third call - _, _, err = accountant.AccountBlob(ctx, 500, quorums) - assert.NoError(t, err) - assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) -} - -func TestBinRotation(t *testing.T) { - reservation := core.ActiveReservation{ - SymbolsPerSec: 1000, - StartTimestamp: 100, - EndTimestamp: 200, - QuorumSplit: []byte{50, 50}, - QuorumNumbers: []uint8{0, 1}, - } - onDemand := core.OnDemandPayment{ - CumulativePayment: big.NewInt(1000), - } - reservationWindow := uint32(1) // Set to 1 second for testing - pricePerSymbol := uint32(1) - minNumSymbols := uint32(100) - - privateKey1, err := crypto.GenerateKey() - assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() - - ctx := context.Background() - quorums := []uint8{0, 1} - - // First call - _, _, err = accountant.AccountBlob(ctx, 800, quorums) - assert.NoError(t, err) - assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) - - // Second call for overflow - _, _, err = accountant.AccountBlob(ctx, 800, quorums) - assert.NoError(t, err) - assert.Equal(t, []uint64{1600, 0, 600}, accountant.binUsages) - - // Wait for bin rotation - time.Sleep(1200 * time.Millisecond) - - _, _, err = accountant.AccountBlob(ctx, 300, quorums) - assert.NoError(t, err) - assert.Equal(t, []uint64{300, 600, 0}, accountant.binUsages) - - // another bin rotation - time.Sleep(1200 * time.Millisecond) - - _, _, err = accountant.AccountBlob(ctx, 500, quorums) + header, _, err = accountant.AccountBlob(ctx, 500, quorums) assert.NoError(t, err) - assert.Equal(t, []uint64{1100, 0, 100}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{800, 800, 0}, mapRecordUsage(accountant.binRecords)), true) } func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { @@ -319,7 +266,6 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() quorums := []uint8{0, 1} @@ -330,11 +276,12 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { wg.Add(1) go func() { defer wg.Done() - for j := 0; j < 5; j++ { - _, _, err := accountant.AccountBlob(ctx, 100, quorums) - assert.NoError(t, err) - time.Sleep(500 * time.Millisecond) - } + // for j := 0; j < 5; j++ { + // fmt.Println("request ", i) + _, _, err := accountant.AccountBlob(ctx, 100, quorums) + assert.NoError(t, err) + time.Sleep(500 * time.Millisecond) + // } }() } @@ -342,7 +289,8 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { wg.Wait() // Check final state - assert.Equal(t, uint64(1000), accountant.binUsages[0]+accountant.binUsages[1]+accountant.binUsages[2]) + usages := mapRecordUsage(accountant.binRecords) + assert.Equal(t, uint64(1000), usages[0]+usages[1]+usages[2]) } func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { @@ -364,7 +312,6 @@ func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() quorums := []uint8{0, 1} now := time.Now().Unix() @@ -375,22 +322,22 @@ func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { assert.Equal(t, meterer.GetBinIndex(uint64(now), reservationWindow), header.BinIndex) metadata := core.ConvertPaymentHeader(header) assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) - assert.Equal(t, []uint64{800, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{800, 0, 0}, mapRecordUsage(accountant.binRecords)), true) // Second call: Allow one overflow header, _, err = accountant.AccountBlob(ctx, 500, quorums) assert.NoError(t, err) metadata = core.ConvertPaymentHeader(header) assert.Equal(t, big.NewInt(0), metadata.CumulativePayment) - assert.Equal(t, []uint64{1300, 0, 300}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{1300, 0, 300}, mapRecordUsage(accountant.binRecords)), true) // Third call: Should use on-demand payment header, _, err = accountant.AccountBlob(ctx, 200, quorums) assert.NoError(t, err) assert.Equal(t, uint32(0), header.BinIndex) metadata = core.ConvertPaymentHeader(header) - assert.Equal(t, big.NewInt(2), metadata.CumulativePayment) - assert.Equal(t, []uint64{1300, 0, 300}, accountant.binUsages) + assert.Equal(t, big.NewInt(200), metadata.CumulativePayment) + assert.Equal(t, isRotation([]uint64{1300, 0, 300}, mapRecordUsage(accountant.binRecords)), true) } func TestAccountBlob_ReservationOverflowReset(t *testing.T) { @@ -412,7 +359,6 @@ func TestAccountBlob_ReservationOverflowReset(t *testing.T) { assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) - defer accountant.Stop() ctx := context.Background() quorums := []uint8{0, 1} @@ -420,20 +366,104 @@ func TestAccountBlob_ReservationOverflowReset(t *testing.T) { // full reservation _, _, err = accountant.AccountBlob(ctx, 1000, quorums) assert.NoError(t, err) - assert.Equal(t, []uint64{1000, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{1000, 0, 0}, mapRecordUsage(accountant.binRecords)), true) // no overflow header, _, err := accountant.AccountBlob(ctx, 500, quorums) assert.NoError(t, err) - assert.Equal(t, []uint64{1000, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{1000, 0, 0}, mapRecordUsage(accountant.binRecords)), true) metadata := core.ConvertPaymentHeader(header) - assert.Equal(t, big.NewInt(5), metadata.CumulativePayment) + assert.Equal(t, big.NewInt(500), metadata.CumulativePayment) - // Wait for bin rotation - time.Sleep(1500 * time.Millisecond) + // Wait for next reservation duration + time.Sleep(time.Duration(reservationWindow) * time.Second) // Third call: Should use new bin and allow overflow again header, _, err = accountant.AccountBlob(ctx, 500, quorums) assert.NoError(t, err) - assert.Equal(t, []uint64{500, 0, 0}, accountant.binUsages) + assert.Equal(t, isRotation([]uint64{1000, 500, 0}, mapRecordUsage(accountant.binRecords)), true) +} + +func TestQuorumCheck(t *testing.T) { + tests := []struct { + name string + quorumNumbers []uint8 + allowedNumbers []uint8 + expectError bool + errorMessage string + }{ + { + name: "valid quorum numbers", + quorumNumbers: []uint8{0, 1}, + allowedNumbers: []uint8{0, 1, 2}, + expectError: false, + }, + { + name: "empty quorum numbers", + quorumNumbers: []uint8{}, + allowedNumbers: []uint8{0, 1}, + expectError: true, + errorMessage: "no quorum numbers provided", + }, + { + name: "invalid quorum number", + quorumNumbers: []uint8{0, 2}, + allowedNumbers: []uint8{0, 1}, + expectError: true, + errorMessage: "provided quorum number 2 not allowed", + }, + { + name: "empty allowed numbers", + quorumNumbers: []uint8{0}, + allowedNumbers: []uint8{}, + expectError: true, + errorMessage: "provided quorum number 0 not allowed", + }, + { + name: "multiple invalid quorums", + quorumNumbers: []uint8{2, 3, 4}, + allowedNumbers: []uint8{0, 1}, + expectError: true, + errorMessage: "provided quorum number 2 not allowed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := QuorumCheck(tt.quorumNumbers, tt.allowedNumbers) + if tt.expectError { + assert.Error(t, err) + assert.Contains(t, err.Error(), tt.errorMessage) + } else { + assert.NoError(t, err) + } + }) + } +} + +func mapRecordUsage(records []BinRecord) []uint64 { + return []uint64{records[0].Usage, records[1].Usage, records[2].Usage} +} + +func isRotation(arrA, arrB []uint64) bool { + n := len(arrA) + if n != len(arrB) { + return false + } + + doubleArrA := append(arrA, arrA...) + // Check if arrB exists in doubleArrA as a subarray + for i := 0; i < n; i++ { + match := true + for j := 0; j < n; j++ { + if doubleArrA[i+j] != arrB[j] { + match = false + break + } + } + if match { + return true + } + } + return false } diff --git a/api/clients/disperser_client.go b/api/clients/disperser_client.go index 69f72a5545..10ab4ca476 100644 --- a/api/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -209,7 +209,7 @@ func (c *disperserClient) DispersePaidBlob(ctx context.Context, data []byte, quo return nil, nil, fmt.Errorf("encountered an error to convert a 32-bytes into a valid field element, please use the correct format where every 32bytes(big-endian) is less than 21888242871839275222246405745257275088548364400416034343698204186575808495617 %w", err) } - header, signature, err := c.accountant.AccountBlob(ctx, uint64(len(data)), quorums) + header, signature, err := c.accountant.AccountBlob(ctx, uint64(encoding.GetBlobLength(uint(len(data)))), quorums) if err != nil { return nil, nil, err } From 752b57cd19d9f71fd23949511fb066b1ffb7933d Mon Sep 17 00:00:00 2001 From: hopeyen Date: Wed, 30 Oct 2024 12:36:36 -0700 Subject: [PATCH 10/17] refactor: comments and minor updates --- api/clients/accountant.go | 8 +- api/clients/config.go | 7 ++ api/clients/disperser_client.go | 8 ++ api/clients/eigenda_client.go | 144 +++++--------------------------- tools/traffic/generator.go | 2 +- tools/traffic/generator_v2.go | 2 +- 6 files changed, 41 insertions(+), 130 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index 089b673b2b..961e1019fd 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -30,7 +30,6 @@ type Accountant struct { binRecords []BinRecord usageLock sync.Mutex cumulativePayment *big.Int - stopRotation chan struct{} paymentSigner core.PaymentSigner } @@ -52,14 +51,17 @@ func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPay minNumSymbols: minNumSymbols, binRecords: []BinRecord{{Index: 0, Usage: 0}, {Index: 1, Usage: 0}, {Index: 2, Usage: 0}}, cumulativePayment: big.NewInt(0), - stopRotation: make(chan struct{}), paymentSigner: paymentSigner, } // TODO: add a routine to refresh the on-chain state occasionally? return &a } -// accountant calculates and records payment information +// BlobPaymentInfo calculates and records payment information. The accountant +// will attempt to use the active reservation first and check for quorum settings, +// then on-demand if the reservation is not available. The returned values are +// bin index for reservation payments and cumulative payment for on-demand payments, +// and both fields are used to create the payment header and signature func (a *Accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quorumNumbers []uint8) (uint32, *big.Int, error) { now := time.Now().Unix() currentBinIndex := meterer.GetBinIndex(uint64(now), a.reservationWindow) diff --git a/api/clients/config.go b/api/clients/config.go index f4d9caa9fb..5dbd78de09 100644 --- a/api/clients/config.go +++ b/api/clients/config.go @@ -60,6 +60,10 @@ type EigenDAClientConfig struct { // that can retrieve blobs but cannot disperse blobs. SignerPrivateKeyHex string + // Payment signer private key in hex encoded format. This key connect to the wallet with payment registered on-chain + // if set to "", will result in a non-paying client and cannot disperse paid blobs. + PaymentSignerPrivateKeyHex string + // Whether to disable TLS for an insecure connection when connecting to a local EigenDA disperser instance. DisableTLS bool @@ -112,6 +116,9 @@ func (c *EigenDAClientConfig) CheckAndSetDefaults() error { if len(c.SignerPrivateKeyHex) > 0 && len(c.SignerPrivateKeyHex) != 64 { return fmt.Errorf("a valid length SignerPrivateKeyHex needs to have 64 bytes") } + if len(c.PaymentSignerPrivateKeyHex) > 0 && len(c.PaymentSignerPrivateKeyHex) != 64 { + return fmt.Errorf("a valid length PaymentSignerPrivateKeyHex needs to have 64 bytes") + } if len(c.RPC) == 0 { return fmt.Errorf("EigenDAClientConfig.RPC not set") diff --git a/api/clients/disperser_client.go b/api/clients/disperser_client.go index 10ab4ca476..2b1c313a72 100644 --- a/api/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -3,6 +3,7 @@ package clients import ( "context" "crypto/tls" + "errors" "fmt" "sync" "time" @@ -190,6 +191,10 @@ func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, quorums // DispersePaidBlob disperses a blob with a payment header and signature. Similar to DisperseBlob but with signed payment header. func (c *disperserClient) DispersePaidBlob(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { + if c.accountant == nil { + return nil, nil, api.NewErrorInternal("not implemented") + } + err := c.initOnceGrpcConnection() if err != nil { return nil, nil, fmt.Errorf("error initializing connection: %w", err) @@ -210,6 +215,9 @@ func (c *disperserClient) DispersePaidBlob(ctx context.Context, data []byte, quo } header, signature, err := c.accountant.AccountBlob(ctx, uint64(encoding.GetBlobLength(uint(len(data)))), quorums) + if header == nil { + return nil, nil, errors.New("accountant returned nil pointer to header") + } if err != nil { return nil, nil, err } diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go index 1df99b1a00..cb9879f833 100644 --- a/api/clients/eigenda_client.go +++ b/api/clients/eigenda_client.go @@ -21,7 +21,6 @@ import ( edasm "github.com/Layr-Labs/eigenda/contracts/bindings/EigenDAServiceManager" "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" - "github.com/Layr-Labs/eigenda/disperser" ) // IEigenDAClient is a wrapper around the DisperserClient interface which @@ -46,7 +45,6 @@ type EigenDAClient struct { ethClient *ethclient.Client edasmCaller *edasm.ContractEigenDAServiceManagerCaller Codec codecs.BlobCodec - accountant Accountant } var _ IEigenDAClient = &EigenDAClient{} @@ -111,10 +109,19 @@ func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClien } disperserConfig := NewConfig(host, port, config.ResponseTimeout, !config.DisableTLS) - paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString([]byte(config.SignerPrivateKeyHex))) - if err != nil { - return nil, fmt.Errorf("new payment signer: %w", err) + + var paymentSigner core.PaymentSigner + if len(config.PaymentSignerPrivateKeyHex) == 64 { + paymentSigner, err = auth.NewPaymentSigner(config.PaymentSignerPrivateKeyHex) + if err != nil { + return nil, fmt.Errorf("new payment signer: %w", err) + } + } else if len(config.PaymentSignerPrivateKeyHex) == 0 { + paymentSigner = auth.NewNoopPaymentSigner() + } else { + return nil, fmt.Errorf("invalid length for signer private key") } + // a subsequent PR contains updates to fill in payment state accountant := NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 0, 0, 0, paymentSigner) disperserClient, err := NewDisperserClient(disperserConfig, signer, accountant) @@ -246,7 +253,13 @@ func (m *EigenDAClient) putBlob(ctxFinality context.Context, rawData []byte, res } // disperse blob // TODO: would be nice to add a trace-id key to the context, to be able to follow requests from batcher->proxy->eigenda - _, requestID, err := m.Client.DisperseBlobAuthenticated(ctxFinality, data, customQuorumNumbers) + var requestID []byte + // clients with a payment signer setting can disperse paid blobs + if len(m.Config.PaymentSignerPrivateKeyHex) > 0 { + _, requestID, err = m.Client.DispersePaidBlob(ctxFinality, data, customQuorumNumbers) + } else { + _, requestID, err = m.Client.DisperseBlobAuthenticated(ctxFinality, data, customQuorumNumbers) + } if err != nil { // DisperserClient returned error is already a grpc error which can be a 400 (eg rate limited) or 500, // so we wrap the error such that clients can still use grpc's status.FromError() function to get the status code. @@ -378,125 +391,6 @@ func (m *EigenDAClient) putBlob(ctxFinality context.Context, rawData []byte, res } } -// PaidPutBlob behaves like PutBlob but with authenticated payment. -func (m EigenDAClient) PaidPutBlob(ctx context.Context, data []byte) (*grpcdisperser.BlobInfo, error) { - resultChan, errorChan := m.PaidPutBlobAsync(ctx, data) - select { // no timeout here because we depend on the configured timeout in PutBlobAsync - case result := <-resultChan: - return result, nil - case err := <-errorChan: - return nil, err - } -} - -func (m EigenDAClient) PaidPutBlobAsync(ctx context.Context, data []byte) (resultChan chan *grpcdisperser.BlobInfo, errChan chan error) { - resultChan = make(chan *grpcdisperser.BlobInfo, 1) - errChan = make(chan error, 1) - go m.paidPutBlob(ctx, data, resultChan, errChan) - return -} - -func (m EigenDAClient) paidPutBlob(ctx context.Context, rawData []byte, resultChan chan *grpcdisperser.BlobInfo, errChan chan error) { - m.Log.Info("Attempting to disperse blob to EigenDA with payment") - - // encode blob - if m.Codec == nil { - errChan <- fmt.Errorf("Codec cannot be nil") - return - } - - data, err := m.Codec.EncodeBlob(rawData) - if err != nil { - errChan <- fmt.Errorf("error encoding blob: %w", err) - return - } - - customQuorumNumbers := make([]uint8, len(m.Config.CustomQuorumIDs)) - for i, e := range m.Config.CustomQuorumIDs { - customQuorumNumbers[i] = uint8(e) - } - // disperse blob - blobStatus, requestID, err := m.Client.DispersePaidBlob(ctx, data, customQuorumNumbers) - if err != nil { - errChan <- fmt.Errorf("error initializing DispersePaidBlob() client: %w", err) - return - } - - // process response - if *blobStatus == disperser.Failed { - m.Log.Error("Unable to disperse blob to EigenDA, aborting", "err", err) - errChan <- fmt.Errorf("reply status is %d", blobStatus) - return - } - - base64RequestID := base64.StdEncoding.EncodeToString(requestID) - m.Log.Info("Blob dispersed to EigenDA, now waiting for confirmation", "requestID", base64RequestID) - - ticker := time.NewTicker(m.Config.StatusQueryRetryInterval) - defer ticker.Stop() - - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, m.Config.StatusQueryTimeout) - defer cancel() - - alreadyWaitingForDispersal := false - alreadyWaitingForFinalization := false - for { - select { - case <-ctx.Done(): - errChan <- fmt.Errorf("timed out waiting for EigenDA blob to confirm blob with request id=%s: %w", base64RequestID, ctx.Err()) - return - case <-ticker.C: - statusRes, err := m.Client.GetBlobStatus(ctx, requestID) - if err != nil { - m.Log.Error("Unable to retrieve blob dispersal status, will retry", "requestID", base64RequestID, "err", err) - continue - } - - switch statusRes.Status { - case grpcdisperser.BlobStatus_PROCESSING, grpcdisperser.BlobStatus_DISPERSING: - // to prevent log clutter, we only log at info level once - if alreadyWaitingForDispersal { - m.Log.Debug("Blob submitted, waiting for dispersal from EigenDA", "requestID", base64RequestID) - } else { - m.Log.Info("Blob submitted, waiting for dispersal from EigenDA", "requestID", base64RequestID) - alreadyWaitingForDispersal = true - } - case grpcdisperser.BlobStatus_FAILED: - m.Log.Error("EigenDA blob dispersal failed in processing", "requestID", base64RequestID, "err", err) - errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing, requestID=%s: %w", base64RequestID, err) - return - case grpcdisperser.BlobStatus_INSUFFICIENT_SIGNATURES: - m.Log.Error("EigenDA blob dispersal failed in processing with insufficient signatures", "requestID", base64RequestID, "err", err) - errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing with insufficient signatures, requestID=%s: %w", base64RequestID, err) - return - case grpcdisperser.BlobStatus_CONFIRMED: - if m.Config.WaitForFinalization { - // to prevent log clutter, we only log at info level once - if alreadyWaitingForFinalization { - m.Log.Debug("EigenDA blob confirmed, waiting for finalization", "requestID", base64RequestID) - } else { - m.Log.Info("EigenDA blob confirmed, waiting for finalization", "requestID", base64RequestID) - alreadyWaitingForFinalization = true - } - } else { - m.Log.Info("EigenDA blob confirmed", "requestID", base64RequestID) - resultChan <- statusRes.Info - return - } - case grpcdisperser.BlobStatus_FINALIZED: - batchHeaderHashHex := fmt.Sprintf("0x%s", hex.EncodeToString(statusRes.Info.BlobVerificationProof.BatchMetadata.BatchHeaderHash)) - m.Log.Info("Successfully dispersed blob to EigenDA", "requestID", base64RequestID, "batchHeaderHash", batchHeaderHashHex) - resultChan <- statusRes.Info - return - default: - errChan <- fmt.Errorf("EigenDA blob dispersal failed in processing with reply status %d", statusRes.Status) - return - } - } - } -} - // Close simply calls Close() on the wrapped disperserClient, to close the grpc connection to the disperser server. // It is thread safe and can be called multiple times. func (c *EigenDAClient) Close() error { diff --git a/tools/traffic/generator.go b/tools/traffic/generator.go index 5afdf0b564..f979ac1770 100644 --- a/tools/traffic/generator.go +++ b/tools/traffic/generator.go @@ -31,7 +31,7 @@ func NewTrafficGenerator(config *Config, signer core.BlobRequestSigner) (*Traffi return nil, fmt.Errorf("new logger: %w", err) } - dispserserClient, err := clients.NewDisperserClient(&config.Config, signer) + dispserserClient, err := clients.NewDisperserClient(&config.Config, signer, nil) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) } diff --git a/tools/traffic/generator_v2.go b/tools/traffic/generator_v2.go index 61575b9c00..a15aabb419 100644 --- a/tools/traffic/generator_v2.go +++ b/tools/traffic/generator_v2.go @@ -88,7 +88,7 @@ func NewTrafficGeneratorV2(config *config.Config) (*Generator, error) { unconfirmedKeyChannel := make(chan *workers.UnconfirmedKey, 100) - disperserClient, err := clients.NewDisperserClient(config.DisperserClientConfig, signer) + disperserClient, err := clients.NewDisperserClient(config.DisperserClientConfig, signer, nil) if err != nil { cancel() return nil, fmt.Errorf("new disperser-client: %w", err) From 3bbec23fbf31816035364f1e47a88504b1fb11d9 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Wed, 30 Oct 2024 18:08:19 -0700 Subject: [PATCH 11/17] refactor: use pointers --- api/clients/accountant.go | 20 ++++++++++---------- api/clients/accountant_test.go | 18 +++++++++--------- api/clients/eigenda_client.go | 2 +- inabox/tests/integration_test.go | 2 +- inabox/tests/payment_test.go | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index 961e1019fd..d07d8d436e 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -17,10 +17,10 @@ type IAccountant interface { AccountBlob(ctx context.Context, data []byte, quorums []uint8) (uint32, uint64, error) } -type Accountant struct { +type accountant struct { // on-chain states - reservation core.ActiveReservation - onDemand core.OnDemandPayment + reservation *core.ActiveReservation + onDemand *core.OnDemandPayment reservationWindow uint32 pricePerSymbol uint32 minNumSymbols uint32 @@ -39,11 +39,11 @@ type BinRecord struct { Usage uint64 } -func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPayment, reservationWindow uint32, pricePerSymbol uint32, minNumSymbols uint32, paymentSigner core.PaymentSigner) *Accountant { +func NewAccountant(reservation *core.ActiveReservation, onDemand *core.OnDemandPayment, reservationWindow uint32, pricePerSymbol uint32, minNumSymbols uint32, paymentSigner core.PaymentSigner) *accountant { //TODO: client storage; currently every instance starts fresh but on-chain or a small store makes more sense // Also client is currently responsible for supplying network params, we need to add RPC in order to be automatic // There's a subsequent PR that handles populating the accountant with on-chain state from the disperser - a := Accountant{ + a := accountant{ reservation: reservation, onDemand: onDemand, reservationWindow: reservationWindow, @@ -62,7 +62,7 @@ func NewAccountant(reservation core.ActiveReservation, onDemand core.OnDemandPay // then on-demand if the reservation is not available. The returned values are // bin index for reservation payments and cumulative payment for on-demand payments, // and both fields are used to create the payment header and signature -func (a *Accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quorumNumbers []uint8) (uint32, *big.Int, error) { +func (a *accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quorumNumbers []uint8) (uint32, *big.Int, error) { now := time.Now().Unix() currentBinIndex := meterer.GetBinIndex(uint64(now), a.reservationWindow) @@ -105,7 +105,7 @@ func (a *Accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quo } // accountant provides and records payment information -func (a *Accountant) AccountBlob(ctx context.Context, numSymbols uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) { +func (a *accountant) AccountBlob(ctx context.Context, numSymbols uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) { binIndex, cumulativePayment, err := a.BlobPaymentInfo(ctx, numSymbols, quorums) if err != nil { return nil, nil, err @@ -129,13 +129,13 @@ func (a *Accountant) AccountBlob(ctx context.Context, numSymbols uint64, quorums // TODO: PaymentCharged and SymbolsCharged copied from meterer, should be refactored // PaymentCharged returns the chargeable price for a given data length -func (a *Accountant) PaymentCharged(numSymbols uint) uint64 { +func (a *accountant) PaymentCharged(numSymbols uint) uint64 { return uint64(a.SymbolsCharged(numSymbols)) * uint64(a.pricePerSymbol) } // SymbolsCharged returns the number of symbols charged for a given data length // being at least MinNumSymbols or the nearest rounded-up multiple of MinNumSymbols. -func (a *Accountant) SymbolsCharged(numSymbols uint) uint32 { +func (a *accountant) SymbolsCharged(numSymbols uint) uint32 { if numSymbols <= uint(a.minNumSymbols) { return a.minNumSymbols } @@ -143,7 +143,7 @@ func (a *Accountant) SymbolsCharged(numSymbols uint) uint32 { return uint32(core.RoundUpDivide(uint(numSymbols), uint(a.minNumSymbols))) * a.minNumSymbols } -func (a *Accountant) GetRelativeBinRecord(index uint32) *BinRecord { +func (a *accountant) GetRelativeBinRecord(index uint32) *BinRecord { relativeIndex := index % 3 if a.binRecords[relativeIndex].Index != uint32(index) { a.binRecords[relativeIndex] = BinRecord{ diff --git a/api/clients/accountant_test.go b/api/clients/accountant_test.go index 443f5746df..0e2a26ec24 100644 --- a/api/clients/accountant_test.go +++ b/api/clients/accountant_test.go @@ -34,7 +34,7 @@ func TestNewAccountant(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) assert.NotNil(t, accountant) assert.Equal(t, reservation, accountant.reservation) @@ -64,7 +64,7 @@ func TestAccountBlob_Reservation(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() symbolLength := uint64(500) @@ -115,7 +115,7 @@ func TestAccountBlob_OnDemand(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() numSymbols := uint64(1500) @@ -144,7 +144,7 @@ func TestAccountBlob_InsufficientOnDemand(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() numSymbols := uint64(2000) @@ -172,7 +172,7 @@ func TestAccountBlobCallSeries(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() quorums := []uint8{0, 1} @@ -222,7 +222,7 @@ func TestAccountBlob_BinRotation(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() quorums := []uint8{0, 1} @@ -265,7 +265,7 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() quorums := []uint8{0, 1} @@ -311,7 +311,7 @@ func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() quorums := []uint8{0, 1} now := time.Now().Unix() @@ -358,7 +358,7 @@ func TestAccountBlob_ReservationOverflowReset(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) ctx := context.Background() quorums := []uint8{0, 1} diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go index cb9879f833..5ede6e3fdf 100644 --- a/api/clients/eigenda_client.go +++ b/api/clients/eigenda_client.go @@ -123,7 +123,7 @@ func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClien } // a subsequent PR contains updates to fill in payment state - accountant := NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 0, 0, 0, paymentSigner) + accountant := NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 0, 0, 0, paymentSigner) disperserClient, err := NewDisperserClient(disperserConfig, signer, accountant) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) diff --git a/inabox/tests/integration_test.go b/inabox/tests/integration_test.go index efdf70120a..05dad45299 100644 --- a/inabox/tests/integration_test.go +++ b/inabox/tests/integration_test.go @@ -47,7 +47,7 @@ var _ = Describe("Inabox Integration", func() { Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer, clients.NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) + }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) Expect(err).To(BeNil()) Expect(disp).To(Not(BeNil())) diff --git a/inabox/tests/payment_test.go b/inabox/tests/payment_test.go index 73def60e13..b70913ebe0 100644 --- a/inabox/tests/payment_test.go +++ b/inabox/tests/payment_test.go @@ -43,7 +43,7 @@ var _ = Describe("Inabox Integration", func() { Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer, clients.NewAccountant(core.ActiveReservation{}, core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) + }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) Expect(disp).To(Not(BeNil())) From 0e5b034a7326f5e523fb5f6606cc11fad9ba4cec Mon Sep 17 00:00:00 2001 From: hopeyen Date: Wed, 30 Oct 2024 18:37:40 -0700 Subject: [PATCH 12/17] refactor: type conversions --- api/clients/accountant.go | 10 ++++-- api/clients/accountant_test.go | 20 ++++++----- api/clients/config.go | 3 ++ api/clients/eigenda_client.go | 2 +- core/auth.go | 4 +-- core/auth/payment_signer.go | 14 +++----- core/auth/payment_signer_test.go | 21 ++++++------ core/data.go | 11 ++++++- disperser/apiserver/payment_test.go | 51 ++++++++++++++--------------- disperser/apiserver/server.go | 5 ++- inabox/tests/integration_test.go | 2 +- inabox/tests/payment_test.go | 2 +- 12 files changed, 79 insertions(+), 66 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index d07d8d436e..619a1f5784 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -13,6 +13,8 @@ import ( "github.com/Layr-Labs/eigenda/core/meterer" ) +var minNumBins uint32 = 3 + type IAccountant interface { AccountBlob(ctx context.Context, data []byte, quorums []uint8) (uint32, uint64, error) } @@ -32,6 +34,7 @@ type accountant struct { cumulativePayment *big.Int paymentSigner core.PaymentSigner + numBins uint32 } type BinRecord struct { @@ -39,7 +42,7 @@ type BinRecord struct { Usage uint64 } -func NewAccountant(reservation *core.ActiveReservation, onDemand *core.OnDemandPayment, reservationWindow uint32, pricePerSymbol uint32, minNumSymbols uint32, paymentSigner core.PaymentSigner) *accountant { +func NewAccountant(reservation *core.ActiveReservation, onDemand *core.OnDemandPayment, reservationWindow uint32, pricePerSymbol uint32, minNumSymbols uint32, paymentSigner core.PaymentSigner, numBins uint32) *accountant { //TODO: client storage; currently every instance starts fresh but on-chain or a small store makes more sense // Also client is currently responsible for supplying network params, we need to add RPC in order to be automatic // There's a subsequent PR that handles populating the accountant with on-chain state from the disperser @@ -52,6 +55,7 @@ func NewAccountant(reservation *core.ActiveReservation, onDemand *core.OnDemandP binRecords: []BinRecord{{Index: 0, Usage: 0}, {Index: 1, Usage: 0}, {Index: 2, Usage: 0}}, cumulativePayment: big.NewInt(0), paymentSigner: paymentSigner, + numBins: max(numBins, minNumBins), } // TODO: add a routine to refresh the on-chain state occasionally? return &a @@ -119,7 +123,7 @@ func (a *accountant) AccountBlob(ctx context.Context, numSymbols uint64, quorums } protoPaymentHeader := pm.ConvertToProtoPaymentHeader() - signature, err := a.paymentSigner.SignBlobPayment(protoPaymentHeader) + signature, err := a.paymentSigner.SignBlobPayment(pm) if err != nil { return nil, nil, err } @@ -144,7 +148,7 @@ func (a *accountant) SymbolsCharged(numSymbols uint) uint32 { } func (a *accountant) GetRelativeBinRecord(index uint32) *BinRecord { - relativeIndex := index % 3 + relativeIndex := index % a.numBins if a.binRecords[relativeIndex].Index != uint32(index) { a.binRecords[relativeIndex] = BinRecord{ Index: uint32(index), diff --git a/api/clients/accountant_test.go b/api/clients/accountant_test.go index 0e2a26ec24..2f74ce0d2c 100644 --- a/api/clients/accountant_test.go +++ b/api/clients/accountant_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/assert" ) +const numBins = uint32(3) + func TestNewAccountant(t *testing.T) { reservation := core.ActiveReservation{ SymbolsPerSec: 100, @@ -34,7 +36,7 @@ func TestNewAccountant(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) assert.NotNil(t, accountant) assert.Equal(t, reservation, accountant.reservation) @@ -64,7 +66,7 @@ func TestAccountBlob_Reservation(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() symbolLength := uint64(500) @@ -115,7 +117,7 @@ func TestAccountBlob_OnDemand(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() numSymbols := uint64(1500) @@ -144,7 +146,7 @@ func TestAccountBlob_InsufficientOnDemand(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() numSymbols := uint64(2000) @@ -172,7 +174,7 @@ func TestAccountBlobCallSeries(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} @@ -222,7 +224,7 @@ func TestAccountBlob_BinRotation(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} @@ -265,7 +267,7 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} @@ -311,7 +313,7 @@ func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} now := time.Now().Unix() @@ -358,7 +360,7 @@ func TestAccountBlob_ReservationOverflowReset(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner) + accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} diff --git a/api/clients/config.go b/api/clients/config.go index 5dbd78de09..c4024a1df5 100644 --- a/api/clients/config.go +++ b/api/clients/config.go @@ -64,6 +64,9 @@ type EigenDAClientConfig struct { // if set to "", will result in a non-paying client and cannot disperse paid blobs. PaymentSignerPrivateKeyHex string + // Payment number of bins indicate how many bins are kept at all times; the minimum is set to 3. + PaymentNumBins uint32 + // Whether to disable TLS for an insecure connection when connecting to a local EigenDA disperser instance. DisableTLS bool diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go index 5ede6e3fdf..b60d1364b7 100644 --- a/api/clients/eigenda_client.go +++ b/api/clients/eigenda_client.go @@ -123,7 +123,7 @@ func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClien } // a subsequent PR contains updates to fill in payment state - accountant := NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 0, 0, 0, paymentSigner) + accountant := NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 0, 0, 0, paymentSigner, config.PaymentNumBins) disperserClient, err := NewDisperserClient(disperserConfig, signer, accountant) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) diff --git a/core/auth.go b/core/auth.go index 349f5b1aff..964d3fdcd0 100644 --- a/core/auth.go +++ b/core/auth.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" geth "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -51,6 +50,7 @@ func VerifySignature(message []byte, accountAddr geth.Address, sig []byte) error } type PaymentSigner interface { - SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) + // SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) + SignBlobPayment(header *PaymentMetadata) ([]byte, error) GetAccountID() string } diff --git a/core/auth/payment_signer.go b/core/auth/payment_signer.go index 2afd16ea6e..b222830406 100644 --- a/core/auth/payment_signer.go +++ b/core/auth/payment_signer.go @@ -4,7 +4,6 @@ import ( "crypto/ecdsa" "fmt" - commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" "github.com/Layr-Labs/eigenda/core" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -32,9 +31,7 @@ func NewPaymentSigner(privateKeyHex string) (*paymentSigner, error) { } // SignBlobPayment signs the payment header and returns the signature -func (s *paymentSigner) SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) { - header.AccountId = s.GetAccountID() - pm := core.ConvertPaymentHeader(header) +func (s *paymentSigner) SignBlobPayment(pm *core.PaymentMetadata) ([]byte, error) { hash, err := pm.Hash() if err != nil { return nil, fmt.Errorf("failed to hash payment header: %w", err) @@ -54,7 +51,7 @@ func NewNoopPaymentSigner() *NoopPaymentSigner { return &NoopPaymentSigner{} } -func (s *NoopPaymentSigner) SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) { +func (s *NoopPaymentSigner) SignBlobPayment(header *core.PaymentMetadata) ([]byte, error) { return nil, fmt.Errorf("noop signer cannot sign blob payment header") } @@ -63,9 +60,8 @@ func (s *NoopPaymentSigner) GetAccountID() string { } // VerifyPaymentSignature verifies the signature against the payment metadata -func VerifyPaymentSignature(paymentHeader *commonpb.PaymentHeader, paymentSignature []byte) error { - pm := core.ConvertPaymentHeader(paymentHeader) - hash, err := pm.Hash() +func VerifyPaymentSignature(paymentHeader *core.PaymentMetadata, paymentSignature []byte) error { + hash, err := paymentHeader.Hash() if err != nil { return fmt.Errorf("failed to hash payment header: %w", err) } @@ -76,7 +72,7 @@ func VerifyPaymentSignature(paymentHeader *commonpb.PaymentHeader, paymentSignat } recoveredAddress := crypto.PubkeyToAddress(*recoveredPubKey) - accountId := common.HexToAddress(paymentHeader.AccountId) + accountId := common.HexToAddress(paymentHeader.AccountID) if recoveredAddress != accountId { return fmt.Errorf("signature address %s does not match account id %s", recoveredAddress.Hex(), accountId.Hex()) } diff --git a/core/auth/payment_signer_test.go b/core/auth/payment_signer_test.go index ecd9ec692d..8bccc07b22 100644 --- a/core/auth/payment_signer_test.go +++ b/core/auth/payment_signer_test.go @@ -2,9 +2,10 @@ package auth_test import ( "encoding/hex" + "math/big" "testing" - commonpb "github.com/Layr-Labs/eigenda/api/grpc/common" + "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" @@ -20,10 +21,10 @@ func TestPaymentSigner(t *testing.T) { require.NoError(t, err) t.Run("SignBlobPayment", func(t *testing.T) { - header := &commonpb.PaymentHeader{ + header := &core.PaymentMetadata{ + AccountID: "", BinIndex: 1, - CumulativePayment: []byte{0x01, 0x02, 0x03}, - AccountId: "", + CumulativePayment: big.NewInt(1), } signature, err := signer.SignBlobPayment(header) @@ -36,10 +37,10 @@ func TestPaymentSigner(t *testing.T) { }) t.Run("VerifyPaymentSignature_InvalidSignature", func(t *testing.T) { - header := &commonpb.PaymentHeader{ + header := &core.PaymentMetadata{ BinIndex: 1, - CumulativePayment: []byte{0x01, 0x02, 0x03}, - AccountId: "", + CumulativePayment: big.NewInt(1), + AccountID: "", } // Create an invalid signature @@ -49,10 +50,10 @@ func TestPaymentSigner(t *testing.T) { }) t.Run("VerifyPaymentSignature_ModifiedHeader", func(t *testing.T) { - header := &commonpb.PaymentHeader{ + header := &core.PaymentMetadata{ BinIndex: 1, - CumulativePayment: []byte{0x01, 0x02, 0x03}, - AccountId: "", + CumulativePayment: big.NewInt(1), + AccountID: "", } signature, err := signer.SignBlobPayment(header) diff --git a/core/data.go b/core/data.go index 970d76746f..e4e990e620 100644 --- a/core/data.go +++ b/core/data.go @@ -572,7 +572,7 @@ func ConvertPaymentHeader(header *commonpb.PaymentHeader) *PaymentMetadata { } } -// Hash returns the Keccak256 hash of the PaymentMetadata +// ConvertToProtoPaymentHeader converts a PaymentMetadata to a protobuf payment header func (pm *PaymentMetadata) ConvertToProtoPaymentHeader() *commonpb.PaymentHeader { return &commonpb.PaymentHeader{ AccountId: pm.AccountID, @@ -581,6 +581,15 @@ func (pm *PaymentMetadata) ConvertToProtoPaymentHeader() *commonpb.PaymentHeader } } +// ConvertToProtoPaymentHeader converts a PaymentMetadata to a protobuf payment header +func ConvertToPaymentMetadata(ph *commonpb.PaymentHeader) *PaymentMetadata { + return &PaymentMetadata{ + AccountID: ph.AccountId, + BinIndex: ph.BinIndex, + CumulativePayment: new(big.Int).SetBytes(ph.CumulativePayment), + } +} + // OperatorInfo contains information about an operator which is stored on the blockchain state, // corresponding to a particular quorum type ActiveReservation struct { diff --git a/disperser/apiserver/payment_test.go b/disperser/apiserver/payment_test.go index 23ac774eda..c049380899 100644 --- a/disperser/apiserver/payment_test.go +++ b/disperser/apiserver/payment_test.go @@ -14,7 +14,6 @@ import ( "github.com/Layr-Labs/eigenda/encoding" "github.com/Layr-Labs/eigenda/encoding/utils/codec" - pbcommon "github.com/Layr-Labs/eigenda/api/grpc/common" pb "github.com/Layr-Labs/eigenda/api/grpc/disperser" "github.com/Layr-Labs/eigenda/core" "github.com/stretchr/testify/assert" @@ -60,17 +59,17 @@ func TestDispersePaidBlob(t *testing.T) { symbolLength := encoding.GetBlobLength(uint(len(data))) // disperse on-demand payment for i := 1; i < 3; i++ { - pm := pbcommon.PaymentHeader{ - AccountId: signer.GetAccountID(), + pm := &core.PaymentMetadata{ + AccountID: signer.GetAccountID(), BinIndex: 0, - CumulativePayment: big.NewInt(int64(int(symbolLength) * i * encoding.BYTES_PER_SYMBOL)).Bytes(), + CumulativePayment: big.NewInt(int64(int(symbolLength) * i * encoding.BYTES_PER_SYMBOL)), } - sig, err := signer.SignBlobPayment(&pm) + sig, err := signer.SignBlobPayment(pm) assert.NoError(t, err) reply, err := dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ Data: data, QuorumNumbers: quorums, - PaymentHeader: &pm, + PaymentHeader: pm.ConvertToProtoPaymentHeader(), PaymentSignature: sig, }) assert.NoError(t, err) @@ -79,17 +78,17 @@ func TestDispersePaidBlob(t *testing.T) { } // exceeded payment limit - pm := pbcommon.PaymentHeader{ - AccountId: signer.GetAccountID(), + pm := &core.PaymentMetadata{ + AccountID: signer.GetAccountID(), BinIndex: 0, - CumulativePayment: big.NewInt(int64(symbolLength*3)*encoding.BYTES_PER_SYMBOL - 1).Bytes(), + CumulativePayment: big.NewInt(int64(symbolLength*3)*encoding.BYTES_PER_SYMBOL - 1), } - sig, err := signer.SignBlobPayment(&pm) + sig, err := signer.SignBlobPayment(pm) assert.NoError(t, err) _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ Data: data, QuorumNumbers: quorums, - PaymentHeader: &pm, + PaymentHeader: pm.ConvertToProtoPaymentHeader(), PaymentSignature: sig, }) assert.Error(t, err) @@ -99,17 +98,17 @@ func TestDispersePaidBlob(t *testing.T) { // TODO: somehow meterer is not defined as a method or field in dispersalServer; reservationWindow we set was 1 for i := 0; i < 2; i++ { binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), 1) - pm = pbcommon.PaymentHeader{ - AccountId: signer.GetAccountID(), + pm := &core.PaymentMetadata{ + AccountID: signer.GetAccountID(), BinIndex: binIndex, - CumulativePayment: big.NewInt(0).Bytes(), + CumulativePayment: big.NewInt(0), } - sig, err = signer.SignBlobPayment(&pm) + sig, err = signer.SignBlobPayment(pm) assert.NoError(t, err) reply, err := dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ Data: data, QuorumNumbers: []uint32{1}, - PaymentHeader: &pm, + PaymentHeader: pm.ConvertToProtoPaymentHeader(), PaymentSignature: sig, }) assert.NoError(t, err) @@ -118,33 +117,33 @@ func TestDispersePaidBlob(t *testing.T) { } binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), 1) - pm = pbcommon.PaymentHeader{ - AccountId: signer.GetAccountID(), + pm = &core.PaymentMetadata{ + AccountID: signer.GetAccountID(), BinIndex: binIndex, - CumulativePayment: big.NewInt(0).Bytes(), + CumulativePayment: big.NewInt(0), } - sig, err = signer.SignBlobPayment(&pm) + sig, err = signer.SignBlobPayment(pm) assert.NoError(t, err) _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ Data: data, QuorumNumbers: []uint32{1}, - PaymentHeader: &pm, + PaymentHeader: pm.ConvertToProtoPaymentHeader(), PaymentSignature: sig, }) assert.Contains(t, err.Error(), "bin has already been filled") // invalid bin index binIndex = meterer.GetBinIndex(uint64(time.Now().Unix())/2, 1) - pm = pbcommon.PaymentHeader{ - AccountId: signer.GetAccountID(), + pm = &core.PaymentMetadata{ + AccountID: signer.GetAccountID(), BinIndex: binIndex, - CumulativePayment: big.NewInt(0).Bytes(), + CumulativePayment: big.NewInt(0), } - sig, err = signer.SignBlobPayment(&pm) + sig, err = signer.SignBlobPayment(pm) _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ Data: data, QuorumNumbers: []uint32{1}, - PaymentHeader: &pm, + PaymentHeader: pm.ConvertToProtoPaymentHeader(), PaymentSignature: sig, }) assert.Contains(t, err.Error(), "invalid bin index for reservation") diff --git a/disperser/apiserver/server.go b/disperser/apiserver/server.go index 5f41d22655..4dc7b3a573 100644 --- a/disperser/apiserver/server.go +++ b/disperser/apiserver/server.go @@ -329,7 +329,6 @@ func (s *DispersalServer) DispersePaidBlob(ctx context.Context, req *pb.Disperse binIndex := req.PaymentHeader.BinIndex cumulativePayment := new(big.Int).SetBytes(req.PaymentHeader.CumulativePayment) //todo: before disperse blob, validate the signature - signature := req.PaymentSignature if err != nil { for _, quorumID := range req.QuorumNumbers { s.metrics.HandleFailedRequest(codes.InvalidArgument.String(), fmt.Sprint(quorumID), len(req.GetData()), "DispersePaidBlob") @@ -338,7 +337,7 @@ func (s *DispersalServer) DispersePaidBlob(ctx context.Context, req *pb.Disperse return nil, api.NewErrorInvalidArg(err.Error()) } - if err = auth.VerifyPaymentSignature(req.GetPaymentHeader(), signature); err != nil { + if err = auth.VerifyPaymentSignature(core.ConvertToPaymentMetadata(req.GetPaymentHeader()), req.PaymentSignature); err != nil { return nil, api.NewErrorInvalidArg("payment signature is invalid") } @@ -1117,7 +1116,7 @@ func (s *DispersalServer) validatePaidRequestAndGetBlob(ctx context.Context, req seenQuorums := make(map[uint8]struct{}) // TODO: validate payment signature against payment metadata - if err = auth.VerifyPaymentSignature(req.GetPaymentHeader(), req.GetPaymentSignature()); err != nil { + if err = auth.VerifyPaymentSignature(core.ConvertToPaymentMetadata(req.GetPaymentHeader()), req.GetPaymentSignature()); err != nil { return nil, fmt.Errorf("payment signature is invalid: %w", err) } // Unlike regular blob dispersal request validation, there's no check with required quorums diff --git a/inabox/tests/integration_test.go b/inabox/tests/integration_test.go index 05dad45299..382454ca45 100644 --- a/inabox/tests/integration_test.go +++ b/inabox/tests/integration_test.go @@ -47,7 +47,7 @@ var _ = Describe("Inabox Integration", func() { Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) + }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner, 3)) Expect(err).To(BeNil()) Expect(disp).To(Not(BeNil())) diff --git a/inabox/tests/payment_test.go b/inabox/tests/payment_test.go index b70913ebe0..b0d245139c 100644 --- a/inabox/tests/payment_test.go +++ b/inabox/tests/payment_test.go @@ -43,7 +43,7 @@ var _ = Describe("Inabox Integration", func() { Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner)) + }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner, 3)) Expect(disp).To(Not(BeNil())) From 5b0d1c6f22df1bc072170065c1437d3c668263db Mon Sep 17 00:00:00 2001 From: hopeyen Date: Thu, 31 Oct 2024 11:20:56 -0700 Subject: [PATCH 13/17] refactor: hardcode required quorum constant --- api/clients/accountant.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index 619a1f5784..d517e28fdc 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -14,6 +14,7 @@ import ( ) var minNumBins uint32 = 3 +var requiredQuorums = []uint8{0, 1} type IAccountant interface { AccountBlob(ctx context.Context, data []byte, quorums []uint8) (uint32, uint64, error) @@ -100,7 +101,7 @@ func (a *accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quo incrementRequired := big.NewInt(int64(a.PaymentCharged(uint(numSymbols)))) a.cumulativePayment.Add(a.cumulativePayment, incrementRequired) if a.cumulativePayment.Cmp(a.onDemand.CumulativePayment) <= 0 { - if err := QuorumCheck(quorumNumbers, []uint8{0, 1}); err != nil { + if err := QuorumCheck(quorumNumbers, requiredQuorums); err != nil { return 0, big.NewInt(0), err } return 0, a.cumulativePayment, nil From 61a4c3bc770afc3f23c8021754c9bfc65f274680 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Thu, 31 Oct 2024 13:51:02 -0700 Subject: [PATCH 14/17] refactor: rm comment and update interface input --- api/clients/accountant.go | 6 ++++-- api/clients/disperser_client.go | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index d517e28fdc..13dd53999b 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -16,10 +16,12 @@ import ( var minNumBins uint32 = 3 var requiredQuorums = []uint8{0, 1} -type IAccountant interface { - AccountBlob(ctx context.Context, data []byte, quorums []uint8) (uint32, uint64, error) +type Accountant interface { + AccountBlob(ctx context.Context, numSymbols uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) } +var _ Accountant = &accountant{} + type accountant struct { // on-chain states reservation *core.ActiveReservation diff --git a/api/clients/disperser_client.go b/api/clients/disperser_client.go index 2b1c313a72..26f7ce2195 100644 --- a/api/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -81,7 +81,7 @@ type disperserClient struct { conn *grpc.ClientConn client disperser_rpc.DisperserClient - accountant *Accountant + accountant Accountant } var _ DisperserClient = &disperserClient{} @@ -106,7 +106,7 @@ var _ DisperserClient = &disperserClient{} // // // Subsequent calls will use the existing connection // status2, requestId2, err := client.DisperseBlob(ctx, otherData, otherQuorums) -func NewDisperserClient(config *Config, signer core.BlobRequestSigner, accountant *Accountant) (*disperserClient, error) { +func NewDisperserClient(config *Config, signer core.BlobRequestSigner, accountant Accountant) (*disperserClient, error) { if err := checkConfigAndSetDefaults(config); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } From 150582d81e1a7a4ab38a88fbd0cb3a50c09c2f98 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Thu, 31 Oct 2024 23:05:44 -0700 Subject: [PATCH 15/17] fix: proto, contracts, tests --- api/clients/accountant.go | 6 +- api/clients/accountant_test.go | 91 +++++++------- api/clients/config.go | 10 -- api/clients/disperser_client.go | 55 --------- api/clients/disperser_client_test.go | 2 +- api/clients/eigenda_client.go | 23 +--- api/clients/mock/disperser_client.go | 21 ---- api/grpc/disperser/disperser.pb.go | 69 +++++------ api/grpc/disperser/disperser_grpc.pb.go | 45 ------- api/proto/disperser/disperser.proto | 7 -- core/auth.go | 1 - core/auth/payment_signer_test.go | 6 +- disperser/apiserver/payment_test.go | 150 ----------------------- disperser/apiserver/server.go | 133 -------------------- disperser/apiserver/server_test.go | 5 +- inabox/tests/integration_test.go | 9 +- inabox/tests/payment_test.go | 156 ------------------------ test/integration_test.go | 4 +- tools/traffic/workers/mock_disperser.go | 9 -- 19 files changed, 98 insertions(+), 704 deletions(-) delete mode 100644 disperser/apiserver/payment_test.go delete mode 100644 inabox/tests/payment_test.go diff --git a/api/clients/accountant.go b/api/clients/accountant.go index 13dd53999b..0c8f0b28e2 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -49,13 +49,17 @@ func NewAccountant(reservation *core.ActiveReservation, onDemand *core.OnDemandP //TODO: client storage; currently every instance starts fresh but on-chain or a small store makes more sense // Also client is currently responsible for supplying network params, we need to add RPC in order to be automatic // There's a subsequent PR that handles populating the accountant with on-chain state from the disperser + binRecords := make([]BinRecord, numBins) + for i := range binRecords { + binRecords[i] = BinRecord{Index: uint32(i), Usage: 0} + } a := accountant{ reservation: reservation, onDemand: onDemand, reservationWindow: reservationWindow, pricePerSymbol: pricePerSymbol, minNumSymbols: minNumSymbols, - binRecords: []BinRecord{{Index: 0, Usage: 0}, {Index: 1, Usage: 0}, {Index: 2, Usage: 0}}, + binRecords: binRecords, cumulativePayment: big.NewInt(0), paymentSigner: paymentSigner, numBins: max(numBins, minNumBins), diff --git a/api/clients/accountant_test.go b/api/clients/accountant_test.go index 2f74ce0d2c..d40f75550f 100644 --- a/api/clients/accountant_test.go +++ b/api/clients/accountant_test.go @@ -3,7 +3,6 @@ package clients import ( "context" "encoding/hex" - "fmt" "math/big" "sync" "testing" @@ -19,14 +18,14 @@ import ( const numBins = uint32(3) func TestNewAccountant(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 100, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(500), } reservationWindow := uint32(6) @@ -35,8 +34,9 @@ func TestNewAccountant(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) assert.NotNil(t, accountant) assert.Equal(t, reservation, accountant.reservation) @@ -49,14 +49,14 @@ func TestNewAccountant(t *testing.T) { } func TestAccountBlob_Reservation(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 200, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(500), } reservationWindow := uint32(5) @@ -65,8 +65,9 @@ func TestAccountBlob_Reservation(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() symbolLength := uint64(500) @@ -100,14 +101,14 @@ func TestAccountBlob_Reservation(t *testing.T) { } func TestAccountBlob_OnDemand(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 200, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(1500), } reservationWindow := uint32(5) @@ -116,8 +117,9 @@ func TestAccountBlob_OnDemand(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() numSymbols := uint64(1500) @@ -135,8 +137,8 @@ func TestAccountBlob_OnDemand(t *testing.T) { } func TestAccountBlob_InsufficientOnDemand(t *testing.T) { - reservation := core.ActiveReservation{} - onDemand := core.OnDemandPayment{ + reservation := &core.ActiveReservation{} + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(500), } reservationWindow := uint32(60) @@ -145,8 +147,9 @@ func TestAccountBlob_InsufficientOnDemand(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() numSymbols := uint64(2000) @@ -157,14 +160,14 @@ func TestAccountBlob_InsufficientOnDemand(t *testing.T) { } func TestAccountBlobCallSeries(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 200, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(1000), } reservationWindow := uint32(5) @@ -173,8 +176,9 @@ func TestAccountBlobCallSeries(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} @@ -208,14 +212,14 @@ func TestAccountBlobCallSeries(t *testing.T) { } func TestAccountBlob_BinRotation(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 1000, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(1000), } reservationWindow := uint32(1) // Set to 1 second for testing @@ -223,14 +227,15 @@ func TestAccountBlob_BinRotation(t *testing.T) { minNumSymbols := uint32(100) privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} // First call - header, _, err := accountant.AccountBlob(ctx, 800, quorums) + _, _, err = accountant.AccountBlob(ctx, 800, quorums) assert.NoError(t, err) assert.Equal(t, isRotation([]uint64{800, 0, 0}, mapRecordUsage(accountant.binRecords)), true) @@ -238,26 +243,25 @@ func TestAccountBlob_BinRotation(t *testing.T) { time.Sleep(1000 * time.Millisecond) // Second call - header, _, err = accountant.AccountBlob(ctx, 300, quorums) + _, _, err = accountant.AccountBlob(ctx, 300, quorums) assert.NoError(t, err) - fmt.Println("shifts ", header.BinIndex%3) assert.Equal(t, isRotation([]uint64{800, 300, 0}, mapRecordUsage(accountant.binRecords)), true) // Third call - header, _, err = accountant.AccountBlob(ctx, 500, quorums) + _, _, err = accountant.AccountBlob(ctx, 500, quorums) assert.NoError(t, err) assert.Equal(t, isRotation([]uint64{800, 800, 0}, mapRecordUsage(accountant.binRecords)), true) } func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 1000, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(1000), } reservationWindow := uint32(1) // Set to 1 second for testing @@ -266,8 +270,9 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} @@ -296,14 +301,14 @@ func TestConcurrentBinRotationAndAccountBlob(t *testing.T) { } func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 200, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(1000), } reservationWindow := uint32(5) @@ -312,8 +317,9 @@ func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} now := time.Now().Unix() @@ -343,14 +349,14 @@ func TestAccountBlob_ReservationWithOneOverflow(t *testing.T) { } func TestAccountBlob_ReservationOverflowReset(t *testing.T) { - reservation := core.ActiveReservation{ + reservation := &core.ActiveReservation{ SymbolsPerSec: 1000, StartTimestamp: 100, EndTimestamp: 200, QuorumSplit: []byte{50, 50}, QuorumNumbers: []uint8{0, 1}, } - onDemand := core.OnDemandPayment{ + onDemand := &core.OnDemandPayment{ CumulativePayment: big.NewInt(1000), } reservationWindow := uint32(1) // Set to 1 second for testing @@ -359,8 +365,9 @@ func TestAccountBlob_ReservationOverflowReset(t *testing.T) { privateKey1, err := crypto.GenerateKey() assert.NoError(t, err) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) - accountant := NewAccountant(&reservation, &onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) + paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey1.D.Bytes())) + assert.NoError(t, err) + accountant := NewAccountant(reservation, onDemand, reservationWindow, pricePerSymbol, minNumSymbols, paymentSigner, numBins) ctx := context.Background() quorums := []uint8{0, 1} @@ -381,7 +388,7 @@ func TestAccountBlob_ReservationOverflowReset(t *testing.T) { time.Sleep(time.Duration(reservationWindow) * time.Second) // Third call: Should use new bin and allow overflow again - header, _, err = accountant.AccountBlob(ctx, 500, quorums) + _, _, err = accountant.AccountBlob(ctx, 500, quorums) assert.NoError(t, err) assert.Equal(t, isRotation([]uint64{1000, 500, 0}, mapRecordUsage(accountant.binRecords)), true) } diff --git a/api/clients/config.go b/api/clients/config.go index c4024a1df5..f4d9caa9fb 100644 --- a/api/clients/config.go +++ b/api/clients/config.go @@ -60,13 +60,6 @@ type EigenDAClientConfig struct { // that can retrieve blobs but cannot disperse blobs. SignerPrivateKeyHex string - // Payment signer private key in hex encoded format. This key connect to the wallet with payment registered on-chain - // if set to "", will result in a non-paying client and cannot disperse paid blobs. - PaymentSignerPrivateKeyHex string - - // Payment number of bins indicate how many bins are kept at all times; the minimum is set to 3. - PaymentNumBins uint32 - // Whether to disable TLS for an insecure connection when connecting to a local EigenDA disperser instance. DisableTLS bool @@ -119,9 +112,6 @@ func (c *EigenDAClientConfig) CheckAndSetDefaults() error { if len(c.SignerPrivateKeyHex) > 0 && len(c.SignerPrivateKeyHex) != 64 { return fmt.Errorf("a valid length SignerPrivateKeyHex needs to have 64 bytes") } - if len(c.PaymentSignerPrivateKeyHex) > 0 && len(c.PaymentSignerPrivateKeyHex) != 64 { - return fmt.Errorf("a valid length PaymentSignerPrivateKeyHex needs to have 64 bytes") - } if len(c.RPC) == 0 { return fmt.Errorf("EigenDAClientConfig.RPC not set") diff --git a/api/clients/disperser_client.go b/api/clients/disperser_client.go index 26f7ce2195..272dccacf2 100644 --- a/api/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -3,7 +3,6 @@ package clients import ( "context" "crypto/tls" - "errors" "fmt" "sync" "time" @@ -55,7 +54,6 @@ type DisperserClient interface { // DisperseBlobAuthenticated disperses a blob with an authenticated request. // The BlobStatus returned will always be PROCESSSING if error is nil. DisperseBlobAuthenticated(ctx context.Context, data []byte, customQuorums []uint8) (*disperser.BlobStatus, []byte, error) - DispersePaidBlob(ctx context.Context, data []byte, customQuorums []uint8) (*disperser.BlobStatus, []byte, error) GetBlobStatus(ctx context.Context, key []byte) (*disperser_rpc.BlobStatusReply, error) RetrieveBlob(ctx context.Context, batchHeaderHash []byte, blobIndex uint32) ([]byte, error) } @@ -189,59 +187,6 @@ func (c *disperserClient) DisperseBlob(ctx context.Context, data []byte, quorums return blobStatus, reply.GetRequestId(), nil } -// DispersePaidBlob disperses a blob with a payment header and signature. Similar to DisperseBlob but with signed payment header. -func (c *disperserClient) DispersePaidBlob(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { - if c.accountant == nil { - return nil, nil, api.NewErrorInternal("not implemented") - } - - err := c.initOnceGrpcConnection() - if err != nil { - return nil, nil, fmt.Errorf("error initializing connection: %w", err) - } - - ctxTimeout, cancel := context.WithTimeout(ctx, c.config.Timeout) - defer cancel() - - quorumNumbers := make([]uint32, len(quorums)) - for i, q := range quorums { - quorumNumbers[i] = uint32(q) - } - - // check every 32 bytes of data are within the valid range for a bn254 field element - _, err = rs.ToFrArray(data) - if err != nil { - return nil, nil, fmt.Errorf("encountered an error to convert a 32-bytes into a valid field element, please use the correct format where every 32bytes(big-endian) is less than 21888242871839275222246405745257275088548364400416034343698204186575808495617 %w", err) - } - - header, signature, err := c.accountant.AccountBlob(ctx, uint64(encoding.GetBlobLength(uint(len(data)))), quorums) - if header == nil { - return nil, nil, errors.New("accountant returned nil pointer to header") - } - if err != nil { - return nil, nil, err - } - - request := &disperser_rpc.DispersePaidBlobRequest{ - Data: data, - QuorumNumbers: quorumNumbers, - PaymentHeader: header, - PaymentSignature: signature, - } - - reply, err := c.client.DispersePaidBlob(ctxTimeout, request) - if err != nil { - return nil, nil, err - } - - blobStatus, err := disperser.FromBlobStatusProto(reply.GetResult()) - if err != nil { - return nil, nil, err - } - - return blobStatus, reply.GetRequestId(), nil -} - func (c *disperserClient) DisperseBlobAuthenticated(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { err := c.initOnceGrpcConnection() if err != nil { diff --git a/api/clients/disperser_client_test.go b/api/clients/disperser_client_test.go index 00277657bd..cd762a34ff 100644 --- a/api/clients/disperser_client_test.go +++ b/api/clients/disperser_client_test.go @@ -14,7 +14,7 @@ import ( func TestPutBlobNoopSigner(t *testing.T) { config := clients.NewConfig("nohost", "noport", time.Second, false) - disperserClient, err := clients.NewDisperserClient(config, auth.NewLocalNoopSigner()) + disperserClient, err := clients.NewDisperserClient(config, auth.NewLocalNoopSigner(), nil) assert.NoError(t, err) test := []byte("test") diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go index b60d1364b7..ec0ca55c4b 100644 --- a/api/clients/eigenda_client.go +++ b/api/clients/eigenda_client.go @@ -110,21 +110,7 @@ func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClien disperserConfig := NewConfig(host, port, config.ResponseTimeout, !config.DisableTLS) - var paymentSigner core.PaymentSigner - if len(config.PaymentSignerPrivateKeyHex) == 64 { - paymentSigner, err = auth.NewPaymentSigner(config.PaymentSignerPrivateKeyHex) - if err != nil { - return nil, fmt.Errorf("new payment signer: %w", err) - } - } else if len(config.PaymentSignerPrivateKeyHex) == 0 { - paymentSigner = auth.NewNoopPaymentSigner() - } else { - return nil, fmt.Errorf("invalid length for signer private key") - } - - // a subsequent PR contains updates to fill in payment state - accountant := NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 0, 0, 0, paymentSigner, config.PaymentNumBins) - disperserClient, err := NewDisperserClient(disperserConfig, signer, accountant) + disperserClient, err := NewDisperserClient(disperserConfig, signer, nil) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) } @@ -253,13 +239,8 @@ func (m *EigenDAClient) putBlob(ctxFinality context.Context, rawData []byte, res } // disperse blob // TODO: would be nice to add a trace-id key to the context, to be able to follow requests from batcher->proxy->eigenda - var requestID []byte // clients with a payment signer setting can disperse paid blobs - if len(m.Config.PaymentSignerPrivateKeyHex) > 0 { - _, requestID, err = m.Client.DispersePaidBlob(ctxFinality, data, customQuorumNumbers) - } else { - _, requestID, err = m.Client.DisperseBlobAuthenticated(ctxFinality, data, customQuorumNumbers) - } + _, requestID, err := m.Client.DisperseBlobAuthenticated(ctxFinality, data, customQuorumNumbers) if err != nil { // DisperserClient returned error is already a grpc error which can be a 400 (eg rate limited) or 500, // so we wrap the error such that clients can still use grpc's status.FromError() function to get the status code. diff --git a/api/clients/mock/disperser_client.go b/api/clients/mock/disperser_client.go index 6784a1afde..3763e81304 100644 --- a/api/clients/mock/disperser_client.go +++ b/api/clients/mock/disperser_client.go @@ -81,27 +81,6 @@ func (c *MockDisperserClient) DisperseBlob(ctx context.Context, data []byte, quo return status, key, err } -func (c *MockDisperserClient) DispersePaidBlob(ctx context.Context, data []byte, quorums []uint8) (*disperser.BlobStatus, []byte, error) { - args := c.Called(data, quorums) - var status *disperser.BlobStatus - if args.Get(0) != nil { - status = (args.Get(0)).(*disperser.BlobStatus) - } - var key []byte - if args.Get(1) != nil { - key = (args.Get(1)).([]byte) - } - var err error - if args.Get(2) != nil { - err = (args.Get(2)).(error) - } - - keyStr := base64.StdEncoding.EncodeToString(key) - c.mockRequestIDStore[keyStr] = data - - return status, key, err -} - func (c *MockDisperserClient) GetBlobStatus(ctx context.Context, key []byte) (*disperser_rpc.BlobStatusReply, error) { args := c.Called(key) var reply *disperser_rpc.BlobStatusReply diff --git a/api/grpc/disperser/disperser.pb.go b/api/grpc/disperser/disperser.pb.go index 3b29214a8e..d10d4d7366 100644 --- a/api/grpc/disperser/disperser.pb.go +++ b/api/grpc/disperser/disperser.pb.go @@ -1432,38 +1432,33 @@ var file_disperser_disperser_proto_rawDesc = []byte{ 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x04, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x53, 0x10, 0x05, 0x12, 0x0e, 0x0a, - 0x0a, 0x44, 0x49, 0x53, 0x50, 0x45, 0x52, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x32, 0xb1, 0x03, + 0x0a, 0x44, 0x49, 0x53, 0x50, 0x45, 0x52, 0x53, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x32, 0xd9, 0x02, 0x0a, 0x09, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, - 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x56, 0x0a, 0x10, 0x44, - 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x12, - 0x22, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, 0x70, - 0x65, 0x72, 0x73, 0x65, 0x50, 0x61, 0x69, 0x64, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, - 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x00, 0x12, 0x5f, 0x0a, 0x19, 0x44, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, - 0x6c, 0x6f, 0x62, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, - 0x12, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x28, 0x01, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, - 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, - 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x00, 0x12, 0x4e, 0x0a, 0x0c, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, - 0x62, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, - 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, - 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x00, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x4c, 0x61, 0x79, 0x72, 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, - 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x65, - 0x72, 0x73, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x5f, 0x0a, 0x19, 0x44, + 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x41, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1f, 0x2e, 0x64, 0x69, 0x73, 0x70, 0x65, + 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x64, 0x69, 0x73, 0x70, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x28, 0x01, 0x30, 0x01, 0x12, 0x4b, 0x0a, 0x0d, + 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, + 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x69, + 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x0c, 0x52, 0x65, 0x74, + 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x1e, 0x2e, 0x64, 0x69, 0x73, 0x70, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, + 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x69, 0x73, 0x70, + 0x65, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x42, 0x6c, + 0x6f, 0x62, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4c, 0x61, 0x79, 0x72, 0x2d, 0x4c, 0x61, 0x62, + 0x73, 0x2f, 0x65, 0x69, 0x67, 0x65, 0x6e, 0x64, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, + 0x70, 0x63, 0x2f, 0x64, 0x69, 0x73, 0x70, 0x65, 0x72, 0x73, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1518,17 +1513,15 @@ var file_disperser_disperser_proto_depIdxs = []int32{ 16, // 12: disperser.BlobVerificationProof.batch_metadata:type_name -> disperser.BatchMetadata 17, // 13: disperser.BatchMetadata.batch_header:type_name -> disperser.BatchHeader 5, // 14: disperser.Disperser.DisperseBlob:input_type -> disperser.DisperseBlobRequest - 6, // 15: disperser.Disperser.DispersePaidBlob:input_type -> disperser.DispersePaidBlobRequest - 1, // 16: disperser.Disperser.DisperseBlobAuthenticated:input_type -> disperser.AuthenticatedRequest - 8, // 17: disperser.Disperser.GetBlobStatus:input_type -> disperser.BlobStatusRequest - 10, // 18: disperser.Disperser.RetrieveBlob:input_type -> disperser.RetrieveBlobRequest - 7, // 19: disperser.Disperser.DisperseBlob:output_type -> disperser.DisperseBlobReply - 7, // 20: disperser.Disperser.DispersePaidBlob:output_type -> disperser.DisperseBlobReply - 2, // 21: disperser.Disperser.DisperseBlobAuthenticated:output_type -> disperser.AuthenticatedReply - 9, // 22: disperser.Disperser.GetBlobStatus:output_type -> disperser.BlobStatusReply - 11, // 23: disperser.Disperser.RetrieveBlob:output_type -> disperser.RetrieveBlobReply - 19, // [19:24] is the sub-list for method output_type - 14, // [14:19] is the sub-list for method input_type + 1, // 15: disperser.Disperser.DisperseBlobAuthenticated:input_type -> disperser.AuthenticatedRequest + 8, // 16: disperser.Disperser.GetBlobStatus:input_type -> disperser.BlobStatusRequest + 10, // 17: disperser.Disperser.RetrieveBlob:input_type -> disperser.RetrieveBlobRequest + 7, // 18: disperser.Disperser.DisperseBlob:output_type -> disperser.DisperseBlobReply + 2, // 19: disperser.Disperser.DisperseBlobAuthenticated:output_type -> disperser.AuthenticatedReply + 9, // 20: disperser.Disperser.GetBlobStatus:output_type -> disperser.BlobStatusReply + 11, // 21: disperser.Disperser.RetrieveBlob:output_type -> disperser.RetrieveBlobReply + 18, // [18:22] is the sub-list for method output_type + 14, // [14:18] is the sub-list for method input_type 14, // [14:14] is the sub-list for extension type_name 14, // [14:14] is the sub-list for extension extendee 0, // [0:14] is the sub-list for field type_name diff --git a/api/grpc/disperser/disperser_grpc.pb.go b/api/grpc/disperser/disperser_grpc.pb.go index aac01c73b2..c6bb719a2b 100644 --- a/api/grpc/disperser/disperser_grpc.pb.go +++ b/api/grpc/disperser/disperser_grpc.pb.go @@ -20,7 +20,6 @@ const _ = grpc.SupportPackageIsVersion7 const ( Disperser_DisperseBlob_FullMethodName = "/disperser.Disperser/DisperseBlob" - Disperser_DispersePaidBlob_FullMethodName = "/disperser.Disperser/DispersePaidBlob" Disperser_DisperseBlobAuthenticated_FullMethodName = "/disperser.Disperser/DisperseBlobAuthenticated" Disperser_GetBlobStatus_FullMethodName = "/disperser.Disperser/GetBlobStatus" Disperser_RetrieveBlob_FullMethodName = "/disperser.Disperser/RetrieveBlob" @@ -43,11 +42,6 @@ type DisperserClient interface { // // INTERNAL (500): serious error, user should NOT retry. DisperseBlob(ctx context.Context, in *DisperseBlobRequest, opts ...grpc.CallOption) (*DisperseBlobReply, error) - // This API require valid payments to accept blob to disperse from clients. - // This executes the dispersal async, i.e. it returns once the request - // is accepted. The client could use GetBlobStatus() API to poll the the - // processing status of the blob. - DispersePaidBlob(ctx context.Context, in *DispersePaidBlobRequest, opts ...grpc.CallOption) (*DisperseBlobReply, error) // DisperseBlobAuthenticated is similar to DisperseBlob, except that it requires the // client to authenticate itself via the AuthenticationData message. The protoco is as follows: // 1. The client sends a DisperseBlobAuthenticated request with the DisperseBlobRequest message @@ -85,15 +79,6 @@ func (c *disperserClient) DisperseBlob(ctx context.Context, in *DisperseBlobRequ return out, nil } -func (c *disperserClient) DispersePaidBlob(ctx context.Context, in *DispersePaidBlobRequest, opts ...grpc.CallOption) (*DisperseBlobReply, error) { - out := new(DisperseBlobReply) - err := c.cc.Invoke(ctx, Disperser_DispersePaidBlob_FullMethodName, in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *disperserClient) DisperseBlobAuthenticated(ctx context.Context, opts ...grpc.CallOption) (Disperser_DisperseBlobAuthenticatedClient, error) { stream, err := c.cc.NewStream(ctx, &Disperser_ServiceDesc.Streams[0], Disperser_DisperseBlobAuthenticated_FullMethodName, opts...) if err != nil { @@ -160,11 +145,6 @@ type DisperserServer interface { // // INTERNAL (500): serious error, user should NOT retry. DisperseBlob(context.Context, *DisperseBlobRequest) (*DisperseBlobReply, error) - // This API require valid payments to accept blob to disperse from clients. - // This executes the dispersal async, i.e. it returns once the request - // is accepted. The client could use GetBlobStatus() API to poll the the - // processing status of the blob. - DispersePaidBlob(context.Context, *DispersePaidBlobRequest) (*DisperseBlobReply, error) // DisperseBlobAuthenticated is similar to DisperseBlob, except that it requires the // client to authenticate itself via the AuthenticationData message. The protoco is as follows: // 1. The client sends a DisperseBlobAuthenticated request with the DisperseBlobRequest message @@ -193,9 +173,6 @@ type UnimplementedDisperserServer struct { func (UnimplementedDisperserServer) DisperseBlob(context.Context, *DisperseBlobRequest) (*DisperseBlobReply, error) { return nil, status.Errorf(codes.Unimplemented, "method DisperseBlob not implemented") } -func (UnimplementedDisperserServer) DispersePaidBlob(context.Context, *DispersePaidBlobRequest) (*DisperseBlobReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method DispersePaidBlob not implemented") -} func (UnimplementedDisperserServer) DisperseBlobAuthenticated(Disperser_DisperseBlobAuthenticatedServer) error { return status.Errorf(codes.Unimplemented, "method DisperseBlobAuthenticated not implemented") } @@ -236,24 +213,6 @@ func _Disperser_DisperseBlob_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } -func _Disperser_DispersePaidBlob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DispersePaidBlobRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(DisperserServer).DispersePaidBlob(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Disperser_DispersePaidBlob_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(DisperserServer).DispersePaidBlob(ctx, req.(*DispersePaidBlobRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _Disperser_DisperseBlobAuthenticated_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(DisperserServer).DisperseBlobAuthenticated(&disperserDisperseBlobAuthenticatedServer{stream}) } @@ -327,10 +286,6 @@ var Disperser_ServiceDesc = grpc.ServiceDesc{ MethodName: "DisperseBlob", Handler: _Disperser_DisperseBlob_Handler, }, - { - MethodName: "DispersePaidBlob", - Handler: _Disperser_DispersePaidBlob_Handler, - }, { MethodName: "GetBlobStatus", Handler: _Disperser_GetBlobStatus_Handler, diff --git a/api/proto/disperser/disperser.proto b/api/proto/disperser/disperser.proto index 7d21ac3f18..f4bfa1384c 100644 --- a/api/proto/disperser/disperser.proto +++ b/api/proto/disperser/disperser.proto @@ -17,13 +17,6 @@ service Disperser { // INTERNAL (500): serious error, user should NOT retry. rpc DisperseBlob(DisperseBlobRequest) returns (DisperseBlobReply) {} - // This API require valid payments to accept blob to disperse from clients. - // This executes the dispersal async, i.e. it returns once the request - // is accepted. The client could use GetBlobStatus() API to poll the the - // processing status of the blob. - rpc DispersePaidBlob(DispersePaidBlobRequest) returns (DisperseBlobReply) {} - - // DisperseBlobAuthenticated is similar to DisperseBlob, except that it requires the // client to authenticate itself via the AuthenticationData message. The protoco is as follows: // 1. The client sends a DisperseBlobAuthenticated request with the DisperseBlobRequest message diff --git a/core/auth.go b/core/auth.go index 964d3fdcd0..7e7669aa21 100644 --- a/core/auth.go +++ b/core/auth.go @@ -50,7 +50,6 @@ func VerifySignature(message []byte, accountAddr geth.Address, sig []byte) error } type PaymentSigner interface { - // SignBlobPayment(header *commonpb.PaymentHeader) ([]byte, error) SignBlobPayment(header *PaymentMetadata) ([]byte, error) GetAccountID() string } diff --git a/core/auth/payment_signer_test.go b/core/auth/payment_signer_test.go index 8bccc07b22..e2c1a172ad 100644 --- a/core/auth/payment_signer_test.go +++ b/core/auth/payment_signer_test.go @@ -22,7 +22,7 @@ func TestPaymentSigner(t *testing.T) { t.Run("SignBlobPayment", func(t *testing.T) { header := &core.PaymentMetadata{ - AccountID: "", + AccountID: signer.GetAccountID(), BinIndex: 1, CumulativePayment: big.NewInt(1), } @@ -40,7 +40,7 @@ func TestPaymentSigner(t *testing.T) { header := &core.PaymentMetadata{ BinIndex: 1, CumulativePayment: big.NewInt(1), - AccountID: "", + AccountID: signer.GetAccountID(), } // Create an invalid signature @@ -53,7 +53,7 @@ func TestPaymentSigner(t *testing.T) { header := &core.PaymentMetadata{ BinIndex: 1, CumulativePayment: big.NewInt(1), - AccountID: "", + AccountID: signer.GetAccountID(), } signature, err := signer.SignBlobPayment(header) diff --git a/disperser/apiserver/payment_test.go b/disperser/apiserver/payment_test.go deleted file mode 100644 index c049380899..0000000000 --- a/disperser/apiserver/payment_test.go +++ /dev/null @@ -1,150 +0,0 @@ -package apiserver_test - -import ( - "context" - "crypto/rand" - "math/big" - "net" - "testing" - "time" - - "github.com/Layr-Labs/eigenda/core/auth" - "github.com/Layr-Labs/eigenda/core/meterer" - "github.com/Layr-Labs/eigenda/core/mock" - "github.com/Layr-Labs/eigenda/encoding" - "github.com/Layr-Labs/eigenda/encoding/utils/codec" - - pb "github.com/Layr-Labs/eigenda/api/grpc/disperser" - "github.com/Layr-Labs/eigenda/core" - "github.com/stretchr/testify/assert" - tmock "github.com/stretchr/testify/mock" - "google.golang.org/grpc/peer" -) - -func TestDispersePaidBlob(t *testing.T) { - - transactor := &mock.MockWriter{} - transactor.On("GetCurrentBlockNumber").Return(uint32(100), nil) - transactor.On("GetQuorumCount").Return(uint8(2), nil) - quorumParams := []core.SecurityParam{ - {QuorumID: 0, AdversaryThreshold: 80, ConfirmationThreshold: 100}, - {QuorumID: 1, AdversaryThreshold: 80, ConfirmationThreshold: 100}, - } - transactor.On("GetQuorumSecurityParams", tmock.Anything).Return(quorumParams, nil) - transactor.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{0, 1}, nil) - - quorums := []uint32{0, 1} - - dispersalServer := newTestServer(transactor, t.Name()) - - data := make([]byte, 1024) - _, err := rand.Read(data) - assert.NoError(t, err) - - data = codec.ConvertByPaddingEmptyByte(data) - - p := &peer.Peer{ - Addr: &net.TCPAddr{ - IP: net.ParseIP("0.0.0.0"), - Port: 51001, - }, - } - ctx := peer.NewContext(context.Background(), p) - - transactor.On("GetRequiredQuorumNumbers", tmock.Anything).Return([]uint8{0, 1}, nil).Twice() - - pk := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeb" - signer := auth.NewPaymentSigner(pk) - - symbolLength := encoding.GetBlobLength(uint(len(data))) - // disperse on-demand payment - for i := 1; i < 3; i++ { - pm := &core.PaymentMetadata{ - AccountID: signer.GetAccountID(), - BinIndex: 0, - CumulativePayment: big.NewInt(int64(int(symbolLength) * i * encoding.BYTES_PER_SYMBOL)), - } - sig, err := signer.SignBlobPayment(pm) - assert.NoError(t, err) - reply, err := dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ - Data: data, - QuorumNumbers: quorums, - PaymentHeader: pm.ConvertToProtoPaymentHeader(), - PaymentSignature: sig, - }) - assert.NoError(t, err) - assert.Equal(t, reply.GetResult(), pb.BlobStatus_PROCESSING) - assert.NotNil(t, reply.GetRequestId()) - } - - // exceeded payment limit - pm := &core.PaymentMetadata{ - AccountID: signer.GetAccountID(), - BinIndex: 0, - CumulativePayment: big.NewInt(int64(symbolLength*3)*encoding.BYTES_PER_SYMBOL - 1), - } - sig, err := signer.SignBlobPayment(pm) - assert.NoError(t, err) - _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ - Data: data, - QuorumNumbers: quorums, - PaymentHeader: pm.ConvertToProtoPaymentHeader(), - PaymentSignature: sig, - }) - assert.Error(t, err) - assert.Contains(t, err.Error(), "request claims a cumulative payment greater than the on-chain deposit") - - // disperse paid reservation (any quorum number) - // TODO: somehow meterer is not defined as a method or field in dispersalServer; reservationWindow we set was 1 - for i := 0; i < 2; i++ { - binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), 1) - pm := &core.PaymentMetadata{ - AccountID: signer.GetAccountID(), - BinIndex: binIndex, - CumulativePayment: big.NewInt(0), - } - sig, err = signer.SignBlobPayment(pm) - assert.NoError(t, err) - reply, err := dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ - Data: data, - QuorumNumbers: []uint32{1}, - PaymentHeader: pm.ConvertToProtoPaymentHeader(), - PaymentSignature: sig, - }) - assert.NoError(t, err) - assert.Equal(t, reply.GetResult(), pb.BlobStatus_PROCESSING) - assert.NotNil(t, reply.GetRequestId()) - - } - binIndex := meterer.GetBinIndex(uint64(time.Now().Unix()), 1) - pm = &core.PaymentMetadata{ - AccountID: signer.GetAccountID(), - BinIndex: binIndex, - CumulativePayment: big.NewInt(0), - } - sig, err = signer.SignBlobPayment(pm) - assert.NoError(t, err) - _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ - Data: data, - QuorumNumbers: []uint32{1}, - PaymentHeader: pm.ConvertToProtoPaymentHeader(), - PaymentSignature: sig, - }) - assert.Contains(t, err.Error(), "bin has already been filled") - - // invalid bin index - binIndex = meterer.GetBinIndex(uint64(time.Now().Unix())/2, 1) - pm = &core.PaymentMetadata{ - AccountID: signer.GetAccountID(), - BinIndex: binIndex, - CumulativePayment: big.NewInt(0), - } - sig, err = signer.SignBlobPayment(pm) - _, err = dispersalServer.DispersePaidBlob(ctx, &pb.DispersePaidBlobRequest{ - Data: data, - QuorumNumbers: []uint32{1}, - PaymentHeader: pm.ConvertToProtoPaymentHeader(), - PaymentSignature: sig, - }) - assert.Contains(t, err.Error(), "invalid bin index for reservation") -} diff --git a/disperser/apiserver/server.go b/disperser/apiserver/server.go index 4dc7b3a573..cc76549d43 100644 --- a/disperser/apiserver/server.go +++ b/disperser/apiserver/server.go @@ -6,7 +6,6 @@ import ( "encoding/binary" "errors" "fmt" - "math/big" "net" "slices" "strings" @@ -319,43 +318,6 @@ func (s *DispersalServer) disperseBlob(ctx context.Context, blob *core.Blob, aut }, nil } -func (s *DispersalServer) DispersePaidBlob(ctx context.Context, req *pb.DispersePaidBlobRequest) (*pb.DisperseBlobReply, error) { - // If EnablePaymentMeter is false, meterer gets set to nil at start - // In that case, the function should not continue. (checking ) - if s.meterer == nil { - return nil, api.NewErrorInternal("payment feature is not enabled") - } - blob, err := s.validatePaidRequestAndGetBlob(ctx, req) - binIndex := req.PaymentHeader.BinIndex - cumulativePayment := new(big.Int).SetBytes(req.PaymentHeader.CumulativePayment) - //todo: before disperse blob, validate the signature - if err != nil { - for _, quorumID := range req.QuorumNumbers { - s.metrics.HandleFailedRequest(codes.InvalidArgument.String(), fmt.Sprint(quorumID), len(req.GetData()), "DispersePaidBlob") - } - s.metrics.HandleInvalidArgRpcRequest("DispersePaidBlob") - return nil, api.NewErrorInvalidArg(err.Error()) - } - - if err = auth.VerifyPaymentSignature(core.ConvertToPaymentMetadata(req.GetPaymentHeader()), req.PaymentSignature); err != nil { - return nil, api.NewErrorInvalidArg("payment signature is invalid") - } - - paymentHeader := core.PaymentMetadata{ - AccountID: blob.RequestHeader.AccountID, - BinIndex: binIndex, - CumulativePayment: cumulativePayment, - } - reply, err := s.disperseBlob(ctx, blob, "", "DispersePaidBlob", &paymentHeader) - if err != nil { - // Note the DispersePaidBlob already updated metrics for this error. - s.logger.Info("failed to disperse blob", "err", err) - } else { - s.metrics.HandleSuccessfulRpcRequest("DispersePaidBlob") - } - return reply, err -} - func (s *DispersalServer) getAccountRate(origin, authenticatedAddress string, quorumID core.QuorumID) (*PerUserRateInfo, string, error) { unauthRates, ok := s.rateConfig.QuorumRateInfos[quorumID] if !ok { @@ -1078,98 +1040,3 @@ func (s *DispersalServer) validateRequestAndGetBlob(ctx context.Context, req *pb return blob, nil } - -// TODO: refactor checks with validateRequestAndGetBlob; most checks are the same, but paid requests have different quorum requirements -func (s *DispersalServer) validatePaidRequestAndGetBlob(ctx context.Context, req *pb.DispersePaidBlobRequest) (*core.Blob, error) { - - data := req.GetData() - blobSize := len(data) - - // The blob size in bytes must be in range [1, maxBlobSize]. - if blobSize > s.maxBlobSize { - return nil, fmt.Errorf("blob size cannot exceed %v Bytes", s.maxBlobSize) - } - if blobSize == 0 { - return nil, fmt.Errorf("blob size must be greater than 0") - } - - if len(req.GetQuorumNumbers()) > 256 { - return nil, errors.New("number of custom_quorum_numbers must not exceed 256") - } - - // validate every 32 bytes is a valid field element - _, err := rs.ToFrArray(data) - if err != nil { - s.logger.Error("failed to convert a 32bytes as a field element", "err", err) - return nil, api.NewErrorInvalidArg(fmt.Sprintf("encountered an error to convert a 32-bytes into a valid field element, please use the correct format where every 32bytes(big-endian) is less than 21888242871839275222246405745257275088548364400416034343698204186575808495617: %v", err)) - } - - quorumConfig, err := s.updateQuorumConfig(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get quorum config: %w", err) - } - - if len(req.GetQuorumNumbers()) > int(quorumConfig.QuorumCount) { - return nil, errors.New("number of custom_quorum_numbers must not exceed number of quorums") - } - - seenQuorums := make(map[uint8]struct{}) - - // TODO: validate payment signature against payment metadata - if err = auth.VerifyPaymentSignature(core.ConvertToPaymentMetadata(req.GetPaymentHeader()), req.GetPaymentSignature()); err != nil { - return nil, fmt.Errorf("payment signature is invalid: %w", err) - } - // Unlike regular blob dispersal request validation, there's no check with required quorums - // Because Reservation has their specific quorum requirements, and on-demand is only allowed and paid to the required quorums. - // Payment specific validations are done within the meterer library. - for i := range req.GetQuorumNumbers() { - - if req.GetQuorumNumbers()[i] > core.MaxQuorumID { - return nil, fmt.Errorf("custom_quorum_numbers must be in range [0, 254], but found %d", req.GetQuorumNumbers()[i]) - } - - quorumID := uint8(req.GetQuorumNumbers()[i]) - if quorumID >= quorumConfig.QuorumCount { - return nil, fmt.Errorf("custom_quorum_numbers must be in range [0, %d], but found %d", s.quorumConfig.QuorumCount-1, quorumID) - } - - if _, ok := seenQuorums[quorumID]; ok { - return nil, fmt.Errorf("custom_quorum_numbers must not contain duplicates") - } - seenQuorums[quorumID] = struct{}{} - - } - - if len(seenQuorums) == 0 { - return nil, fmt.Errorf("the blob must be sent to at least one quorum") - } - - params := make([]*core.SecurityParam, len(seenQuorums)) - i := 0 - for quorumID := range seenQuorums { - params[i] = &core.SecurityParam{ - QuorumID: core.QuorumID(quorumID), - AdversaryThreshold: quorumConfig.SecurityParams[quorumID].AdversaryThreshold, - ConfirmationThreshold: quorumConfig.SecurityParams[quorumID].ConfirmationThreshold, - } - err = params[i].Validate() - if err != nil { - return nil, fmt.Errorf("invalid request: %w", err) - } - i++ - } - - header := core.BlobRequestHeader{ - BlobAuthHeader: core.BlobAuthHeader{ - AccountID: req.PaymentHeader.AccountId, - }, - SecurityParams: params, - } - - blob := &core.Blob{ - RequestHeader: header, - Data: data, - } - - return blob, nil -} diff --git a/disperser/apiserver/server_test.go b/disperser/apiserver/server_test.go index 18e4321ae8..ce5bce89cc 100644 --- a/disperser/apiserver/server_test.go +++ b/disperser/apiserver/server_test.go @@ -749,7 +749,10 @@ func newTestServer(transactor core.Writer, testName string) *apiserver.Dispersal panic("failed to create offchain store") } mt := meterer.NewMeterer(meterer.Config{}, mockState, store, logger) - mt.ChainPaymentState.RefreshOnchainPaymentState(context.Background(), nil) + err = mt.ChainPaymentState.RefreshOnchainPaymentState(context.Background(), nil) + if err != nil { + panic("failed to make initial query to the on-chain state") + } ratelimiter := ratelimit.NewRateLimiter(prometheus.NewRegistry(), globalParams, bucketStore, logger) rateConfig := apiserver.RateConfig{ diff --git a/inabox/tests/integration_test.go b/inabox/tests/integration_test.go index 382454ca45..0ae11b82a4 100644 --- a/inabox/tests/integration_test.go +++ b/inabox/tests/integration_test.go @@ -4,17 +4,14 @@ import ( "bytes" "context" "crypto/rand" - "encoding/hex" "math/big" "time" "github.com/Layr-Labs/eigenda/api/clients" disperserpb "github.com/Layr-Labs/eigenda/api/grpc/disperser" rollupbindings "github.com/Layr-Labs/eigenda/contracts/bindings/MockRollup" - "github.com/Layr-Labs/eigenda/core" "github.com/Layr-Labs/eigenda/core/auth" "github.com/Layr-Labs/eigenda/disperser" - "github.com/ethereum/go-ethereum/crypto" "github.com/Layr-Labs/eigenda/encoding/utils/codec" . "github.com/onsi/ginkgo/v2" @@ -39,15 +36,11 @@ var _ = Describe("Inabox Integration", func() { privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" signer := auth.NewLocalBlobRequestSigner(privateKeyHex) - privateKey, err := crypto.HexToECDSA(privateKeyHex[2:]) // Remove "0x" prefix - paymentSigner, err := auth.NewPaymentSigner(hex.EncodeToString(privateKey.D.Bytes())) - Expect(err).To(BeNil()) - disp, err := clients.NewDisperserClient(&clients.Config{ Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner, 3)) + }, signer, nil) Expect(err).To(BeNil()) Expect(disp).To(Not(BeNil())) diff --git a/inabox/tests/payment_test.go b/inabox/tests/payment_test.go deleted file mode 100644 index b0d245139c..0000000000 --- a/inabox/tests/payment_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package integration_test - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/hex" - "time" - - "github.com/Layr-Labs/eigenda/api/clients" - disperserpb "github.com/Layr-Labs/eigenda/api/grpc/disperser" - "github.com/Layr-Labs/eigenda/core" - "github.com/Layr-Labs/eigenda/core/auth" - "github.com/Layr-Labs/eigenda/disperser" - "github.com/Layr-Labs/eigenda/encoding" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/Layr-Labs/eigenda/encoding/utils/codec" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -var _ = Describe("Inabox Integration", func() { - It("test payment metering", func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) - defer cancel() - - gasTipCap, gasFeeCap, err := ethClient.GetLatestGasCaps(ctx) - Expect(err).To(BeNil()) - - privateKeyHex := "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcded" - signer := auth.NewLocalBlobRequestSigner(privateKeyHex) - // Disperser configs: rsv window 60s, min chargeable size 100 bytes, price per chargeable 100, global limit 500 - // -> need to check the mock, can't just use any account for the disperser client, consider using static wallets... - - // say with dataLength of 150 bytes, within a window, we can send 7 blobs with overflow of 50 bytes - // the later requests is then 250 bytes, try send 4 blobs within a second, 2 of them would fail but not charged for - // wait for a second, retry, and that should allow ondemand to work - privateKey, err := crypto.HexToECDSA(privateKeyHex[2:]) // Remove "0x" prefix - Expect(err).To(BeNil()) - paymentSigner := auth.NewPaymentSigner(hex.EncodeToString(privateKey.D.Bytes())) - disp := clients.NewDisperserClient(&clients.Config{ - Hostname: "localhost", - Port: "32003", - Timeout: 10 * time.Second, - }, signer, clients.NewAccountant(&core.ActiveReservation{}, &core.OnDemandPayment{}, 60, 128, 128, paymentSigner, 3)) - - Expect(disp).To(Not(BeNil())) - - blobLength := uint32(4) - data := make([]byte, blobLength*encoding.BYTES_PER_SYMBOL) - _, err = rand.Read(data) - Expect(err).To(BeNil()) - - paddedData := codec.ConvertByPaddingEmptyByte(data) - - // requests that count towards either reservation or payments - paidBlobStatus := []disperser.BlobStatus{} - paidKeys := [][]byte{} - reservationBytesLimit := 1024 - paymentLimit := 512 - // TODO: payment calculation unit consistency - for i := 0; i < (int(reservationBytesLimit+paymentLimit))/int(blobLength); i++ { - blobStatus, key, err := disp.DispersePaidBlob(ctx, paddedData, []uint8{0}) - Expect(err).To(BeNil()) - Expect(key).To(Not(BeNil())) - Expect(blobStatus).To(Not(BeNil())) - Expect(*blobStatus).To(Equal(disperser.Processing)) - paidBlobStatus = append(paidBlobStatus, *blobStatus) - paidKeys = append(paidKeys, key) - } - - // requests that aren't covered by reservation or on-demand payment - blobStatus, key, err := disp.DispersePaidBlob(ctx, paddedData, []uint8{0}) - Expect(err).To(Not(BeNil())) - Expect(key).To(BeNil()) - Expect(blobStatus).To(BeNil()) - - ticker := time.NewTicker(time.Second * 1) - defer ticker.Stop() - - var replies = make([]*disperserpb.BlobStatusReply, len(paidBlobStatus)) - // now make sure all the paid blobs get confirmed - loop: - for { - select { - case <-ctx.Done(): - Fail("timed out") - case <-ticker.C: - notConfirmed := false - for i, key := range paidKeys { - reply, err := disp.GetBlobStatus(context.Background(), key) - Expect(err).To(BeNil()) - Expect(reply).To(Not(BeNil())) - status, err := disperser.FromBlobStatusProto(reply.GetStatus()) - Expect(err).To(BeNil()) - if *status != disperser.Confirmed { - notConfirmed = true - } - replies[i] = reply - paidBlobStatus[i] = *status - } - - if notConfirmed { - mineAnvilBlocks(numConfirmations + 1) - continue - } - - for _, reply := range replies { - blobHeader := blobHeaderFromProto(reply.GetInfo().GetBlobHeader()) - verificationProof := blobVerificationProofFromProto(reply.GetInfo().GetBlobVerificationProof()) - opts, err := ethClient.GetNoSendTransactOpts() - Expect(err).To(BeNil()) - tx, err := mockRollup.PostCommitment(opts, blobHeader, verificationProof) - Expect(err).To(BeNil()) - tx, err = ethClient.UpdateGas(ctx, tx, nil, gasTipCap, gasFeeCap) - Expect(err).To(BeNil()) - err = ethClient.SendTransaction(ctx, tx) - Expect(err).To(BeNil()) - mineAnvilBlocks(numConfirmations + 1) - _, err = ethClient.EnsureTransactionEvaled(ctx, tx, "PostCommitment") - Expect(err).To(BeNil()) - } - - break loop - } - } - for _, status := range paidBlobStatus { - Expect(status).To(Equal(disperser.Confirmed)) - } - - ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - for _, reply := range replies { - retrieved, err := retrievalClient.RetrieveBlob(ctx, - [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeaderHash()), - reply.GetInfo().GetBlobVerificationProof().GetBlobIndex(), - uint(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber()), - [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), - 0, // retrieve blob 1 from quorum 0 - ) - Expect(err).To(BeNil()) - restored := codec.RemoveEmptyByteFromPaddedBytes(retrieved) - Expect(bytes.TrimRight(restored, "\x00")).To(Equal(bytes.TrimRight(data, "\x00"))) - - _, err = retrievalClient.RetrieveBlob(ctx, - [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeaderHash()), - reply.GetInfo().GetBlobVerificationProof().GetBlobIndex(), - uint(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetReferenceBlockNumber()), - [32]byte(reply.GetInfo().GetBlobVerificationProof().GetBatchMetadata().GetBatchHeader().GetBatchRoot()), - 1, // retrieve blob 1 from quorum 1 - ) - Expect(err).NotTo(BeNil()) - } - }) -}) diff --git a/test/integration_test.go b/test/integration_test.go index ffb1a90b71..738dd60a92 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -291,8 +291,8 @@ func mustMakeDisperser(t *testing.T, cst core.IndexedChainState, store disperser panic("failed to make initial query to the on-chain state") } - meterer := meterer.NewMeterer(meterer.Config{}, mockState, offchainStore, logger) - server := apiserver.NewDispersalServer(serverConfig, store, tx, logger, disperserMetrics, meterer, ratelimiter, rateConfig, testMaxBlobSize) + mt := meterer.NewMeterer(meterer.Config{}, mockState, offchainStore, logger) + server := apiserver.NewDispersalServer(serverConfig, store, tx, logger, disperserMetrics, mt, ratelimiter, rateConfig, testMaxBlobSize) return TestDisperser{ batcher: batcher, diff --git a/tools/traffic/workers/mock_disperser.go b/tools/traffic/workers/mock_disperser.go index 22e960f4fb..43d9880ce2 100644 --- a/tools/traffic/workers/mock_disperser.go +++ b/tools/traffic/workers/mock_disperser.go @@ -33,15 +33,6 @@ func (m *MockDisperserClient) DisperseBlobAuthenticated( return args.Get(0).(*disperser.BlobStatus), args.Get(1).([]byte), args.Error(2) } -func (m *MockDisperserClient) DispersePaidBlob( - ctx context.Context, - data []byte, - customQuorums []uint8) (*disperser.BlobStatus, []byte, error) { - - args := m.mock.Called(data, customQuorums) - return args.Get(0).(*disperser.BlobStatus), args.Get(1).([]byte), args.Error(2) -} - func (m *MockDisperserClient) GetBlobStatus(ctx context.Context, key []byte) (*disperser_rpc.BlobStatusReply, error) { args := m.mock.Called(key) return args.Get(0).(*disperser_rpc.BlobStatusReply), args.Error(1) From d065607f6fb490a98f4e13381a710ba6d6acb309 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Fri, 1 Nov 2024 08:47:14 -0700 Subject: [PATCH 16/17] fix: try to fix inabox integration test --- inabox/deploy/config.go | 24 ++++++++++-------------- inabox/deploy/env_vars.go | 8 -------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/inabox/deploy/config.go b/inabox/deploy/config.go index 1de79f0032..01795d65fe 100644 --- a/inabox/deploy/config.go +++ b/inabox/deploy/config.go @@ -159,20 +159,16 @@ func (env *Config) generateChurnerVars(ind int, graphUrl, logPath, grpcPort stri // Generates disperser .env func (env *Config) generateDisperserVars(ind int, key, address, logPath, dbPath, grpcPort string) DisperserVars { v := DisperserVars{ - DISPERSER_SERVER_S3_BUCKET_NAME: "test-eigenda-blobstore", - DISPERSER_SERVER_DYNAMODB_TABLE_NAME: "test-BlobMetadata", - DISPERSER_SERVER_RATE_BUCKET_TABLE_NAME: "", - DISPERSER_SERVER_RATE_BUCKET_STORE_SIZE: "100000", - DISPERSER_SERVER_GRPC_PORT: grpcPort, - DISPERSER_SERVER_ENABLE_METRICS: "true", - DISPERSER_SERVER_ENABLE_PAYMENT_METERER: "true", - DISPERSER_SERVER_RESERVATIONS_TABLE_NAME: "reservations_inabox", - DISPERSER_SERVER_ON_DEMAND_TABLE_NAME: "on_demand_inabox", - DISPERSER_SERVER_GLOBAL_RATE_TABLE_NAME: "global_rate_inabox", - DISPERSER_SERVER_METRICS_HTTP_PORT: "9093", - DISPERSER_SERVER_CHAIN_RPC: "", - DISPERSER_SERVER_PRIVATE_KEY: "123", - DISPERSER_SERVER_NUM_CONFIRMATIONS: "0", + DISPERSER_SERVER_S3_BUCKET_NAME: "test-eigenda-blobstore", + DISPERSER_SERVER_DYNAMODB_TABLE_NAME: "test-BlobMetadata", + DISPERSER_SERVER_RATE_BUCKET_TABLE_NAME: "", + DISPERSER_SERVER_RATE_BUCKET_STORE_SIZE: "100000", + DISPERSER_SERVER_GRPC_PORT: grpcPort, + DISPERSER_SERVER_ENABLE_METRICS: "true", + DISPERSER_SERVER_METRICS_HTTP_PORT: "9093", + DISPERSER_SERVER_CHAIN_RPC: "", + DISPERSER_SERVER_PRIVATE_KEY: "123", + DISPERSER_SERVER_NUM_CONFIRMATIONS: "0", DISPERSER_SERVER_REGISTERED_QUORUM_ID: "0,1", DISPERSER_SERVER_TOTAL_UNAUTH_BYTE_RATE: "10000000,10000000", diff --git a/inabox/deploy/env_vars.go b/inabox/deploy/env_vars.go index 2c654b17d5..f701ce64d1 100644 --- a/inabox/deploy/env_vars.go +++ b/inabox/deploy/env_vars.go @@ -76,14 +76,6 @@ type DisperserVars struct { DISPERSER_SERVER_RETRIEVAL_BLOB_RATE string DISPERSER_SERVER_RETRIEVAL_BYTE_RATE string - - DISPERSER_SERVER_ENABLE_PAYMENT_METERER string - - DISPERSER_SERVER_RESERVATIONS_TABLE_NAME string - - DISPERSER_SERVER_ON_DEMAND_TABLE_NAME string - - DISPERSER_SERVER_GLOBAL_RATE_TABLE_NAME string } func (vars DisperserVars) getEnvMap() map[string]string { From 54f2300dd56dc1c9adc9203b040e7cf900284313 Mon Sep 17 00:00:00 2001 From: hopeyen Date: Sat, 2 Nov 2024 05:06:34 -0700 Subject: [PATCH 17/17] refactor: address last feedbacks --- api/clients/accountant.go | 2 +- api/clients/disperser_client.go | 6 +----- api/clients/disperser_client_test.go | 2 +- api/clients/eigenda_client.go | 2 +- inabox/tests/integration_test.go | 2 +- inabox/tests/ratelimit_test.go | 2 +- tools/traffic/generator.go | 2 +- tools/traffic/generator_v2.go | 2 +- 8 files changed, 8 insertions(+), 12 deletions(-) diff --git a/api/clients/accountant.go b/api/clients/accountant.go index 0c8f0b28e2..122ec3eeae 100644 --- a/api/clients/accountant.go +++ b/api/clients/accountant.go @@ -115,7 +115,7 @@ func (a *accountant) BlobPaymentInfo(ctx context.Context, numSymbols uint64, quo return 0, big.NewInt(0), fmt.Errorf("neither reservation nor on-demand payment is available") } -// accountant provides and records payment information +// AccountBlob accountant provides and records payment information func (a *accountant) AccountBlob(ctx context.Context, numSymbols uint64, quorums []uint8) (*commonpb.PaymentHeader, []byte, error) { binIndex, cumulativePayment, err := a.BlobPaymentInfo(ctx, numSymbols, quorums) if err != nil { diff --git a/api/clients/disperser_client.go b/api/clients/disperser_client.go index 272dccacf2..87101547aa 100644 --- a/api/clients/disperser_client.go +++ b/api/clients/disperser_client.go @@ -78,8 +78,6 @@ type disperserClient struct { // instead of a real network connection for eg. conn *grpc.ClientConn client disperser_rpc.DisperserClient - - accountant Accountant } var _ DisperserClient = &disperserClient{} @@ -104,7 +102,7 @@ var _ DisperserClient = &disperserClient{} // // // Subsequent calls will use the existing connection // status2, requestId2, err := client.DisperseBlob(ctx, otherData, otherQuorums) -func NewDisperserClient(config *Config, signer core.BlobRequestSigner, accountant Accountant) (*disperserClient, error) { +func NewDisperserClient(config *Config, signer core.BlobRequestSigner) (*disperserClient, error) { if err := checkConfigAndSetDefaults(config); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } @@ -112,8 +110,6 @@ func NewDisperserClient(config *Config, signer core.BlobRequestSigner, accountan config: config, signer: signer, // conn and client are initialized lazily - - accountant: accountant, }, nil } diff --git a/api/clients/disperser_client_test.go b/api/clients/disperser_client_test.go index cd762a34ff..00277657bd 100644 --- a/api/clients/disperser_client_test.go +++ b/api/clients/disperser_client_test.go @@ -14,7 +14,7 @@ import ( func TestPutBlobNoopSigner(t *testing.T) { config := clients.NewConfig("nohost", "noport", time.Second, false) - disperserClient, err := clients.NewDisperserClient(config, auth.NewLocalNoopSigner(), nil) + disperserClient, err := clients.NewDisperserClient(config, auth.NewLocalNoopSigner()) assert.NoError(t, err) test := []byte("test") diff --git a/api/clients/eigenda_client.go b/api/clients/eigenda_client.go index ec0ca55c4b..3231f29541 100644 --- a/api/clients/eigenda_client.go +++ b/api/clients/eigenda_client.go @@ -110,7 +110,7 @@ func NewEigenDAClient(log log.Logger, config EigenDAClientConfig) (*EigenDAClien disperserConfig := NewConfig(host, port, config.ResponseTimeout, !config.DisableTLS) - disperserClient, err := NewDisperserClient(disperserConfig, signer, nil) + disperserClient, err := NewDisperserClient(disperserConfig, signer) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) } diff --git a/inabox/tests/integration_test.go b/inabox/tests/integration_test.go index 0ae11b82a4..f257d3e2b8 100644 --- a/inabox/tests/integration_test.go +++ b/inabox/tests/integration_test.go @@ -40,7 +40,7 @@ var _ = Describe("Inabox Integration", func() { Hostname: "localhost", Port: "32003", Timeout: 10 * time.Second, - }, signer, nil) + }, signer) Expect(err).To(BeNil()) Expect(disp).To(Not(BeNil())) diff --git a/inabox/tests/ratelimit_test.go b/inabox/tests/ratelimit_test.go index 89e270fa71..1f72cbe015 100644 --- a/inabox/tests/ratelimit_test.go +++ b/inabox/tests/ratelimit_test.go @@ -111,7 +111,7 @@ func testRatelimit(t *testing.T, testConfig *deploy.Config, c ratelimitTestCase) Hostname: "localhost", Port: testConfig.Dispersers[0].DISPERSER_SERVER_GRPC_PORT, Timeout: 10 * time.Second, - }, nil, nil) + }, nil) assert.NoError(t, err) assert.NotNil(t, disp) diff --git a/tools/traffic/generator.go b/tools/traffic/generator.go index f979ac1770..5afdf0b564 100644 --- a/tools/traffic/generator.go +++ b/tools/traffic/generator.go @@ -31,7 +31,7 @@ func NewTrafficGenerator(config *Config, signer core.BlobRequestSigner) (*Traffi return nil, fmt.Errorf("new logger: %w", err) } - dispserserClient, err := clients.NewDisperserClient(&config.Config, signer, nil) + dispserserClient, err := clients.NewDisperserClient(&config.Config, signer) if err != nil { return nil, fmt.Errorf("new disperser-client: %w", err) } diff --git a/tools/traffic/generator_v2.go b/tools/traffic/generator_v2.go index a15aabb419..61575b9c00 100644 --- a/tools/traffic/generator_v2.go +++ b/tools/traffic/generator_v2.go @@ -88,7 +88,7 @@ func NewTrafficGeneratorV2(config *config.Config) (*Generator, error) { unconfirmedKeyChannel := make(chan *workers.UnconfirmedKey, 100) - disperserClient, err := clients.NewDisperserClient(config.DisperserClientConfig, signer, nil) + disperserClient, err := clients.NewDisperserClient(config.DisperserClientConfig, signer) if err != nil { cancel() return nil, fmt.Errorf("new disperser-client: %w", err)