Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

disperser meterer for payments #779

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
435e46d
feat: reservation with mocked interfaces
hopeyen Sep 26, 2024
c5858cf
feat: on-demand with mocked interfaces
hopeyen Sep 30, 2024
57c83d7
refactor: creatTable's in util
hopeyen Sep 30, 2024
8eaa2a8
refactor: clean up dead code
hopeyen Sep 30, 2024
41fd0c8
feat: add meterer to disperser api server
hopeyen Sep 30, 2024
b14bf86
feat: chargeable size calc and some config flags
hopeyen Oct 1, 2024
8548415
refactor: remove some explicit mocked payment state
hopeyen Oct 1, 2024
1b65acf
feat: reservation bin limit config
hopeyen Oct 2, 2024
671df9b
refactor: rm global index validation check for on-demand
hopeyen Oct 2, 2024
533e6d3
fix: check cumulative payments with on-chain state upperbd
hopeyen Oct 2, 2024
13f8fde
refactor: sorted key, query fn with ordering, payment num type
hopeyen Oct 2, 2024
4afd390
refactor: rm dead code, add quoromNumber in ActiveReservations
hopeyen Oct 2, 2024
1af7037
feat: reservation quorom check and tests
hopeyen Oct 2, 2024
d93c91c
feat: ondemand quorom check and tests
hopeyen Oct 2, 2024
e8386ab
refactor: more memory efficient integer types
hopeyen Oct 2, 2024
e1d454b
refactor: rm dup fields BlobSize -> DataLength
hopeyen Oct 2, 2024
18ddb8a
feat: eigenda client local accountant
hopeyen Oct 3, 2024
24977c1
refactor: rm unnecessary fields: nonce, version
hopeyen Oct 3, 2024
ba7bb66
refactor: accountant sign over blob header fields
hopeyen Oct 3, 2024
dd2ab52
fix: no accounting in normal dispersal blob
hopeyen Oct 4, 2024
500ecc4
chore: inabox payments e2e test
hopeyen Oct 4, 2024
75acda3
feat: grpc paid version impl
hopeyen Oct 4, 2024
243a2f9
chore: add dummy state for testing (later add dummy contract)
hopeyen Oct 4, 2024
219acf5
chore: passing payment e2e inabox test with some hardcoded values
hopeyen Oct 5, 2024
9239918
fix: implement PaidDisperseBlob for mockDisperserClient
hopeyen Oct 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions api/clients/accountant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package clients

import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"math/big"
"time"

"github.com/Layr-Labs/eigenda/core"
"github.com/Layr-Labs/eigenda/disperser/meterer"
"github.com/ethereum/go-ethereum/common"
)

type IAccountant interface {
AccountBlob(ctx context.Context, data []byte, quorums []uint8) (uint32, uint64, error)
}

type Accountant struct {
// on-chain states
reservation meterer.ActiveReservation
onDemand meterer.OnDemandPayment
reservationWindow uint32
pricePerChargeable uint32
minChargeableSize uint32

// local accounting
// contains 3 bins; 0 for current bin, 1 for next bin, 2 for overflowed bin
binUsages []uint64
cumulativePayment uint64
stopRotation chan struct{}

domainSigner *meterer.EIP712Signer
accountantSigner *ecdsa.PrivateKey
}

func NewAccountant(reservation meterer.ActiveReservation, onDemand meterer.OnDemandPayment, reservationWindow uint32, pricePerChargeable uint32, minChargeableSize uint32, accountantSigner *ecdsa.PrivateKey) Accountant {
//todo: refactor signing strategy; EIP712 -> serializer? private key -> Local blob request signer?
chainID := big.NewInt(17000)
verifyingContract := common.HexToAddress("0x1234000000000000000000000000000000000000")

a := Accountant{
reservation: reservation,
onDemand: onDemand,
reservationWindow: reservationWindow,
pricePerChargeable: pricePerChargeable,
minChargeableSize: minChargeableSize,
binUsages: []uint64{0, 0, 0},
cumulativePayment: 0,
stopRotation: make(chan struct{}),
domainSigner: meterer.NewEIP712Signer(chainID, verifyingContract),
accountantSigner: accountantSigner,
}
go a.startBinRotation()
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() {
// Shift bins: bin_i to bin_{i-1}, add 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, uint64, error) {
//TODO: do we need to lock the binUsages here in case the rotation happens in the middle of the function?
currentBinUsage := a.binUsages[0]
currentBinIndex := meterer.GetCurrentBinIndex(a.reservationWindow)

// first attempt to use the active reservation
if currentBinUsage+dataLength <= a.reservation.DataRate {
a.binUsages[0] += dataLength
return currentBinIndex, 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 && currentBinUsage < a.reservation.DataRate && dataLength <= a.reservation.DataRate {
fmt.Println("in overflow:", currentBinUsage, dataLength, a.reservation.DataRate)
a.binUsages[0] += dataLength
a.binUsages[2] += currentBinUsage + dataLength - a.reservation.DataRate
return currentBinIndex, 0, nil
}

// reservation not available, attempt on-demand
//todo: rollback if disperser respond with some type of rejection?
incrementRequired := uint64(max(uint32(dataLength), a.minChargeableSize)) * uint64(a.pricePerChargeable) / uint64(a.minChargeableSize)
a.cumulativePayment += incrementRequired
if a.cumulativePayment <= uint64(a.onDemand.CumulativePayment) {
return 0, a.cumulativePayment, nil
}
return 0, 0, errors.New("Accountant cannot approve payment for this blob")
}

// accountant provides and records payment information
func (a *Accountant) AccountBlob(ctx context.Context, dataLength uint64, quorums []uint8) (*meterer.BlobHeader, error) {
binIndex, cumulativePayment, err := a.BlobPaymentInfo(ctx, dataLength)
if err != nil {
return nil, err
}

// TODO: have client first generate a commitment, then pass it here
commitment := core.NewG1Point(big.NewInt(0), big.NewInt(1))
header, err := meterer.ConstructBlobHeader(a.domainSigner, binIndex, cumulativePayment, *commitment, uint32(dataLength), quorums, a.accountantSigner)
if err != nil {
return nil, err
}
return header, nil
}
Loading
Loading