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

[payments] meterer structs and helpers #789

Merged
merged 15 commits into from
Oct 16, 2024
Merged

[payments] meterer structs and helpers #789

merged 15 commits into from
Oct 16, 2024

Conversation

hopeyen
Copy link
Contributor

@hopeyen hopeyen commented Oct 7, 2024

Why are these changes needed?

adding structures used by payment meterer, with utility functions and unit tests

Checks

  • I've made sure the lint is passing in this PR.
  • I've made sure the tests are passing. Note that there might be a few flaky tests, in that case, please comment that they are not relevant.
  • I've checked the new test coverage and the coverage percentage didn't drop.
  • Testing Strategy
    • Unit tests
    • Integration tests
    • This PR is not tested :(

@hopeyen hopeyen changed the title payment meterer helpers [payments] meterer structs and helpers Oct 8, 2024
core/meterer/meterer.go Show resolved Hide resolved
}

update := map[string]types.AttributeValue{
"BinUsage": &types.AttributeValueMemberN{Value: strconv.FormatUint(uint64(size), 10)},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: size is already a uint64

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! updated UpdateItemIncrement function to IncrementBy that only takes in a key name and a value uint64

"BinUsage": &types.AttributeValueMemberN{Value: strconv.FormatUint(uint64(size), 10)},
}

fmt.Println("increment the item in a table", s.reservationTableName)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove or convert to log?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed!

}

// Find all reservation bins for a given account
func (s *OffchainStore) FindReservationBins(ctx context.Context, accountID string) ([]ReservationBin, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, this was for later exposing API to the user, but removed for now

type BlobHeader struct {
// Existing fields
Commitment core.G1Point
DataLength uint32
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This should be the length in number of symbols.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment for this, though should I update the name to be something like NumSymbols?

@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from ae6e034 to c55e7c0 Compare October 8, 2024 17:57
@hopeyen hopeyen changed the base branch from master to hope/dynamodb-updates October 8, 2024 17:57
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch 4 times, most recently from f553c37 to 6ae70ef Compare October 8, 2024 18:02
@@ -38,7 +38,7 @@ var (

type Item = map[string]types.AttributeValue
type Key = map[string]types.AttributeValue
type ExpresseionValues = map[string]types.AttributeValue
type ExpresseionValues = map[string]types.AttributeValue // is this a typo? ExpressionValues?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol yeah.. want to update this in a separate PR?

TxnBroadcastTimeout time.Duration
}

// network parameters that should be published on-chain. We currently configure these params through disperser env vars.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for any comments on a method/struct/field, start by the name of the method/struct/field. It will be recognized by godoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, thanks for the tip!

core/meterer/meterer.go Show resolved Hide resolved
//
// If the payment is valid, the meterer will add the blob header to its state and return a success response to the disperser API server.
// if any of the checks fail, the meterer will return a failure response to the disperser API server.
var OnDemandQuorumNumbers = []uint8{0, 1}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These probably shouldn't be hardcoded

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think they should be published on-chain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have them onchain depending on what you need.
QuorumCount here and required quorums here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that this is a read function from transactor, so will do corresponding updates :)

}

// EIP712Signer handles EIP-712 signing operations
type EIP712Signer struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This signature scheme will likely be shared for validating a request. Should this live outside meterer package? Maybe somewhere like core/auth?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense, moved PaymentMetadata to core/data.go, and all the signing to core/auth! If we enforce meterer to handle everything PaymentMetadata, I can move it back to meterer package 🤔

return nil, fmt.Errorf("table names cannot be empty")
}

err = CreateReservationTable(cfg, reservationTableName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tables should be created outside the application code (devops). We can check if the tables with corresponding names exist in the constructor and error out if they don't exist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, updated to use check and error instead of making db changes

},
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
{
IndexName: aws.String("AccountIDIndex"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this index? Looks like we already key by AccountID

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

During testing, I found that this IndexName was needed to enable QueryIndex. I referenced the pattern like [here] with BlobStatus key and StatusIndex index (

IndexName: aws.String(statusIndexName),
).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah if you need to query by something other than the table's default keys, then building indexes on those attributes may help. However, looks like we can query by the table's default keys, in which case we can query from the table directly

},
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
{
IndexName: aws.String("AccountIDIndex"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

func DummyCommitment() core.G1Point {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem used anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is used in later PRs but I've removed it since we are removing commitment from the Payment header

return *commitment
}

func CreateReservationTable(clientConfig commonaws.ClientConfig, tableName string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we can collapse these tables into a single table design. Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 something like this?
My main consideration is the read/write performance based on the evenness of key distributions, and think it is clearer if different payment methods live in different tables, but I'm interested in learning the advantages of combining these into a single table

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a suggestion in the design doc. We can discuss it there

@hopeyen hopeyen force-pushed the hope/dynamodb-updates branch from feb5543 to a8a6ca9 Compare October 8, 2024 23:55
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch 2 times, most recently from 49e938b to 5b50c0e Compare October 9, 2024 17:00
@hopeyen hopeyen force-pushed the hope/dynamodb-updates branch 2 times, most recently from af1d9fe to a6810c2 Compare October 9, 2024 19:40
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch 2 times, most recently from cc85c19 to e7a8e3a Compare October 9, 2024 21:58
@@ -2,8 +2,10 @@ package dynamodb

Copy link
Contributor

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

Copy link
Contributor Author

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

{Name: "chainId", Type: "uint256"},
{Name: "verifyingContract", Type: "address"},
},
"PaymentMetadata": []apitypes.Type{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this depend on the structure of BlobHeader in v2? (i.e. would signature over BlobHeader be sufficient without one over PaymentMetadata?)
Do we need this struct signed in both v1 & v2?

Copy link
Contributor Author

@hopeyen hopeyen Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think payment metadata will depend on BlobHeader v2 in which PaymentMetadata includes BlobHeader's hash, a field like BlobKey.
This struct would always be signed, but I guess payments is currently disjointed from blob information. We could discuss whether it is okay to let payment be "blob-agnostic" in v1, and only check blob's dataLength against the paid amount?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to support both v1 and v2, what if we were to add a BlobKey field here which is not populated in v1?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need signature over PaymentMetadata at all?
Disperser should validate the request independently i.e. by validating signature over the challenge parameter in v1, and by validating signature over blob key in v2.
Could downstream metering library assume the passed in payment metadata is correct?

@@ -470,3 +470,36 @@ func (cb Bundles) FromEncodedBundles(eb EncodedBundles) (Bundles, error) {
}
return c, nil
}

// PaymentMetadata represents the header information for a blob
type PaymentMetadata struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be independent of what BlobHeader looks like?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

type Config struct {
// for rate limiting 2^64 ~= 18 exabytes per second; 2^32 ~= 4GB/s
// for payments 2^64 ~= 18M Eth; 2^32 ~= 4ETH
GlobalBytesPerSecond uint64 // Global rate limit in bytes per second for on-demand payments
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's nicer to put comment for field description above the definition, starting with the name of the field

ChainReadTimeout time.Duration // Timeout for reading payment state from chain
}

// disperser API server will receive requests from clients. these requests will be with a blobHeader with payments information (CumulativePayments, BinIndex, and Signature)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Meterer handles payment accounting across different accounts. Disperser API server receives requests from clients and each request contains a blob header....

Comment on lines 36 to 37
ChainState OnchainPayment
OffchainStore OffchainStore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: brief description of what gets stored on which store and how they're used differently?

},
GlobalSecondaryIndexes: []types.GlobalSecondaryIndex{
{
IndexName: aws.String("AccountIDIndex"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah if you need to query by something other than the table's default keys, then building indexes on those attributes may help. However, looks like we can query by the table's default keys, in which case we can query from the table directly

@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from e7a8e3a to 7c0c037 Compare October 10, 2024 23:38
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from 7c0c037 to 7854687 Compare October 10, 2024 23:44
@hopeyen hopeyen changed the base branch from hope/dynamodb-updates to master October 10, 2024 23:46
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch 3 times, most recently from c8c1f3e to ac0c882 Compare October 11, 2024 00:48
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from ac0c882 to fab84e3 Compare October 11, 2024 00:50
t.Run("SignPaymentMetadata", func(t *testing.T) {
signature, err := signer.SignPaymentMetadata(header, privateKey)
require.NoError(t, err)
assert.NotEmpty(t, signature)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing you can do is use a contract script like this to generate the same signature on chain and compare.

contract GenerateHashes is Script {

}

// NewEIP712Signer creates a new EIP712Signer instance
func NewEIP712Signer(chainID *big.Int, verifyingContract common.Address) *EIP712Signer {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the verifyingContract ever have a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, verifyingContract would only affect the output signature and the recovery

{Name: "chainId", Type: "uint256"},
{Name: "verifyingContract", Type: "address"},
},
"PaymentMetadata": []apitypes.Type{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to support both v1 and v2, what if we were to add a BlobKey field here which is not populated in v1?

{Name: "accountID", Type: "string"},
{Name: "binIndex", Type: "uint32"},
{Name: "cumulativePayment", Type: "uint64"},
{Name: "dataLength", Type: "uint32"},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the dataLength would be needed in v2 if we also have the blob key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could I assume meterer will receive validated blob header which contains payment metadata and the quorum numbers and length? If so I could remove validateSignature from the meterer process and make a new blobHeader struct?

return signature, nil
}

func convertUint8SliceToMap(params []uint8) []string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

convertUint8SliceToStringSlice?

@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from c255424 to aae5395 Compare October 15, 2024 22:21
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from aae5395 to 4a50bf6 Compare October 15, 2024 22:21
@@ -408,3 +409,17 @@ func (c *Client) readItems(ctx context.Context, tableName string, keys []Key) ([

return items, nil
}

// TableCheck checks if a table exists and can be described
func (c *Client) TableCheck(ctx context.Context, name string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: TableExists?

core/data.go Outdated
return crypto.Keccak256(data)
}

type TokenAmount uint64 // TODO: change to uint128
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be big.Int?

Copy link
Contributor Author

@hopeyen hopeyen Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated I ended up deleting this type alias and just using big.Int directly

Config

// ChainState reads on-chain payment state periodically and cache it in memory
ChainState OnchainPayment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OnchainPayment? ChainState sounds like the ChainState struct in chainio 😭

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 chainio gets merged??

// OnchainPaymentState is an interface for getting information about the current chain state for payments.
type OnchainPayment interface {
GetCurrentBlockNumber(ctx context.Context) (uint32, error)
CurrentOnchainPaymentState(ctx context.Context, tx *eth.Transactor) (OnchainPaymentState, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use reader/writer from chainio to replace transactor?

return *commitment
}

func CreateReservationTable(clientConfig commonaws.ClientConfig, tableName string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a suggestion in the design doc. We can discuss it there

@hopeyen hopeyen force-pushed the hope/meterer-helpers branch 2 times, most recently from 3545713 to 0d0dabf Compare October 16, 2024 17:24
@hopeyen hopeyen force-pushed the hope/meterer-helpers branch from 0d0dabf to 9d5663a Compare October 16, 2024 17:31
core/data.go Outdated
}

type OnDemandPayment struct {
CumulativePayment big.Int // Total amount deposited by the user
Copy link
Collaborator

Choose a reason for hiding this comment

The 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. CumulativePayment *big.Int, as most methods consume the pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sg, updated

@hopeyen hopeyen merged commit aa7b798 into master Oct 16, 2024
10 checks passed
@hopeyen hopeyen deleted the hope/meterer-helpers branch October 16, 2024 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants