-
Notifications
You must be signed in to change notification settings - Fork 193
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
[payments] meterer structs and helpers #789
Changes from 14 commits
547257b
f066d70
1e3a10b
1b2c991
e6e4ea2
7ce96d5
c487395
be254b8
99a7c98
7854687
4f1e931
fab84e3
4a50bf6
9d5663a
ef739df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,12 @@ import ( | |
"encoding/binary" | ||
"errors" | ||
"fmt" | ||
"math/big" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/encoding" | ||
"github.com/consensys/gnark-crypto/ecc/bn254" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
) | ||
|
||
type AccountID = string | ||
|
@@ -291,7 +293,7 @@ func (b *BlobHeader) EncodedSizeAllQuorums() int64 { | |
size := int64(0) | ||
for _, quorum := range b.QuorumInfos { | ||
|
||
size += int64(roundUpDivide(b.Length*percentMultiplier*encoding.BYTES_PER_SYMBOL, uint(quorum.ConfirmationThreshold-quorum.AdversaryThreshold))) | ||
size += int64(RoundUpDivide(b.Length*percentMultiplier*encoding.BYTES_PER_SYMBOL, uint(quorum.ConfirmationThreshold-quorum.AdversaryThreshold))) | ||
} | ||
return size | ||
} | ||
|
@@ -470,3 +472,48 @@ func (cb Bundles) FromEncodedBundles(eb EncodedBundles) (Bundles, error) { | |
} | ||
return c, nil | ||
} | ||
|
||
// PaymentMetadata represents the header information for a blob | ||
type PaymentMetadata struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be independent of what BlobHeader looks like? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm we could add a field like blob key or blob pointer? this would mean we need to do blob lookup. With these current fields, we don't need the blob or blob header to do metering |
||
// Existing fields | ||
AccountID string | ||
|
||
// New fields | ||
BinIndex uint32 | ||
// TODO: we are thinking the contract can use uint128 for cumulative payment, | ||
// but the definition on v2 uses uint64. Double check with team. | ||
CumulativePayment uint64 | ||
} | ||
|
||
// Hash returns the Keccak256 hash of the PaymentMetadata | ||
func (pm *PaymentMetadata) Hash() []byte { | ||
// Create a byte slice to hold the serialized data | ||
data := make([]byte, 0, len(pm.AccountID)+12) | ||
|
||
data = append(data, []byte(pm.AccountID)...) | ||
|
||
binIndexBytes := make([]byte, 4) | ||
binary.BigEndian.PutUint32(binIndexBytes, pm.BinIndex) | ||
data = append(data, binIndexBytes...) | ||
|
||
paymentBytes := make([]byte, 8) | ||
binary.BigEndian.PutUint64(paymentBytes, pm.CumulativePayment) | ||
data = append(data, paymentBytes...) | ||
|
||
return crypto.Keccak256(data) | ||
} | ||
|
||
// OperatorInfo contains information about an operator which is stored on the blockchain state, | ||
// corresponding to a particular quorum | ||
type ActiveReservation struct { | ||
DataRate uint64 // Bandwidth per reservation bin | ||
StartTimestamp uint64 // Unix timestamp that's valid for basically eternity | ||
EndTimestamp uint64 | ||
|
||
QuorumNumbers []uint8 | ||
QuorumSplit []byte // ordered mapping of quorum number to payment split; on-chain validation should ensure split <= 100 | ||
} | ||
|
||
type OnDemandPayment struct { | ||
CumulativePayment big.Int // Total amount deposited by the user | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's common practice to use a pointer for big.Int, i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sg, updated |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package meterer | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigensdk-go/logging" | ||
) | ||
|
||
// Config contains network parameters that should be published on-chain. We currently configure these params through disperser env vars. | ||
type Config struct { | ||
ian-shim marked this conversation as resolved.
Show resolved
Hide resolved
ian-shim marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// GlobalBytesPerSecond is the rate limit in bytes per second for on-demand payments | ||
GlobalBytesPerSecond uint64 | ||
// MinChargeableSize is the minimum size of a chargeable unit in bytes, used as a floor for on-demand payments | ||
MinChargeableSize uint32 | ||
// PricePerChargeable is the price per chargeable unit in gwei, used for on-demand payments | ||
PricePerChargeable uint32 | ||
// ReservationWindow is the duration of all reservations in seconds, used to calculate bin indices | ||
ReservationWindow uint32 | ||
|
||
// ChainReadTimeout is the timeout for reading payment state from chain | ||
ChainReadTimeout time.Duration | ||
} | ||
|
||
// Meterer handles payment accounting across different accounts. Disperser API server receives requests from clients and each request contains a blob header | ||
// with payments information (CumulativePayments, BinIndex, and Signature). Disperser will pass the blob header to the meterer, which will check if the | ||
// payments information is valid. | ||
type Meterer struct { | ||
Config | ||
|
||
// ChainState reads on-chain payment state periodically and cache it in memory | ||
ChainState OnchainPayment | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we perhaps move it later?🫠 similar to replacing transactor with reader, I would prefer creating a separate PR after both of these are merged or at least |
||
// OffchainStore uses DynamoDB to track metering and used to validate requests | ||
OffchainStore OffchainStore | ||
|
||
logger logging.Logger | ||
} | ||
|
||
func NewMeterer( | ||
config Config, | ||
paymentChainState OnchainPayment, | ||
offchainStore OffchainStore, | ||
logger logging.Logger, | ||
) (*Meterer, error) { | ||
// TODO: create a separate thread to pull from the chain and update chain state | ||
return &Meterer{ | ||
Config: config, | ||
|
||
ChainState: paymentChainState, | ||
OffchainStore: offchainStore, | ||
|
||
logger: logger.With("component", "Meterer"), | ||
}, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package meterer_test | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"fmt" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
commonaws "github.com/Layr-Labs/eigenda/common/aws" | ||
commondynamodb "github.com/Layr-Labs/eigenda/common/aws/dynamodb" | ||
"github.com/Layr-Labs/eigenda/core/meterer" | ||
"github.com/Layr-Labs/eigenda/core/mock" | ||
"github.com/Layr-Labs/eigenda/inabox/deploy" | ||
"github.com/ethereum/go-ethereum/crypto" | ||
"github.com/ory/dockertest/v3" | ||
|
||
"github.com/Layr-Labs/eigensdk-go/logging" | ||
) | ||
|
||
var ( | ||
dockertestPool *dockertest.Pool | ||
dockertestResource *dockertest.Resource | ||
dynamoClient *commondynamodb.Client | ||
clientConfig commonaws.ClientConfig | ||
privateKey1 *ecdsa.PrivateKey | ||
privateKey2 *ecdsa.PrivateKey | ||
mt *meterer.Meterer | ||
|
||
deployLocalStack bool | ||
localStackPort = "4566" | ||
paymentChainState = &mock.MockOnchainPaymentState{} | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
setup(m) | ||
code := m.Run() | ||
teardown() | ||
os.Exit(code) | ||
} | ||
|
||
func setup(_ *testing.M) { | ||
|
||
deployLocalStack = !(os.Getenv("DEPLOY_LOCALSTACK") == "false") | ||
if !deployLocalStack { | ||
localStackPort = os.Getenv("LOCALSTACK_PORT") | ||
} | ||
|
||
if deployLocalStack { | ||
var err error | ||
dockertestPool, dockertestResource, err = deploy.StartDockertestWithLocalstackContainer(localStackPort) | ||
if err != nil { | ||
teardown() | ||
panic("failed to start localstack container") | ||
} | ||
} | ||
|
||
loggerConfig := common.DefaultLoggerConfig() | ||
logger, err := common.NewLogger(loggerConfig) | ||
if err != nil { | ||
teardown() | ||
panic("failed to create logger") | ||
} | ||
|
||
clientConfig = commonaws.ClientConfig{ | ||
Region: "us-east-1", | ||
AccessKey: "localstack", | ||
SecretAccessKey: "localstack", | ||
EndpointURL: fmt.Sprintf("http://0.0.0.0:%s", localStackPort), | ||
} | ||
|
||
dynamoClient, err = commondynamodb.NewClient(clientConfig, logger) | ||
if err != nil { | ||
teardown() | ||
panic("failed to create dynamodb client") | ||
} | ||
|
||
privateKey1, err = crypto.GenerateKey() | ||
if err != nil { | ||
teardown() | ||
panic("failed to generate private key") | ||
} | ||
privateKey2, err = crypto.GenerateKey() | ||
if err != nil { | ||
teardown() | ||
panic("failed to generate private key") | ||
} | ||
|
||
logger = logging.NewNoopLogger() | ||
config := meterer.Config{ | ||
PricePerChargeable: 1, | ||
MinChargeableSize: 1, | ||
GlobalBytesPerSecond: 1000, | ||
ReservationWindow: 60, | ||
ChainReadTimeout: 3 * time.Second, | ||
} | ||
|
||
err = meterer.CreateReservationTable(clientConfig, "reservations") | ||
if err != nil { | ||
teardown() | ||
panic("failed to create reservation table") | ||
} | ||
err = meterer.CreateOnDemandTable(clientConfig, "ondemand") | ||
if err != nil { | ||
teardown() | ||
panic("failed to create ondemand table") | ||
} | ||
err = meterer.CreateGlobalReservationTable(clientConfig, "global") | ||
if err != nil { | ||
teardown() | ||
panic("failed to create global reservation table") | ||
} | ||
|
||
store, err := meterer.NewOffchainStore( | ||
clientConfig, | ||
"reservations", | ||
"ondemand", | ||
"global", | ||
logger, | ||
) | ||
|
||
if err != nil { | ||
teardown() | ||
panic("failed to create offchain store") | ||
} | ||
|
||
// add some default sensible configs | ||
mt, err = meterer.NewMeterer( | ||
config, | ||
paymentChainState, | ||
store, | ||
logging.NewNoopLogger(), | ||
// metrics.NewNoopMetrics(), | ||
) | ||
|
||
if err != nil { | ||
teardown() | ||
panic("failed to create meterer") | ||
} | ||
} | ||
|
||
func teardown() { | ||
if deployLocalStack { | ||
deploy.PurgeDockertestResources(dockertestPool, dockertestResource) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rebasing to master should hide these changes that have already been merged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh just realized that this branch was configured to merge into the dynamo-update branch. updated the merge base so the changes should be hidden now