-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[payments] meterer structs and helpers (#789)
- Loading branch information
Showing
14 changed files
with
1,160 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
// 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 | ||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.