Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion tests/e2e/p/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ var _ = e2e.DescribePChain("[Workflow]", func() {
tests.Outf("{{green}} minimal validator stake: %d {{/}}\n", minValStake)
tests.Outf("{{green}} minimal delegator stake: %d {{/}}\n", minDelStake)

tests.Outf("{{blue}} fetching minimal stake amounts {{/}}\n")
feeCfg, err := pChainClient.GetDynamicFeeConfig(e2e.DefaultContext())
require.NoError(err)
tests.Outf("{{green}} fee config: %v {{/}}\n", feeCfg)

tests.Outf("{{blue}} fetching X-chain tx fee {{/}}\n")
infoClient := info.NewClient(nodeURI.URI)
staticFees, err := infoClient.GetTxFee(e2e.DefaultContext())
Expand Down Expand Up @@ -161,7 +166,7 @@ var _ = e2e.DescribePChain("[Workflow]", func() {
require.NoError(err)

// retrieve fees paid for the tx
feeCalc := fee.NewDynamicCalculator(commonfee.NewCalculator(nextGasPrice, nextGasCap))
feeCalc := fee.NewDynamicCalculator(commonfee.NewCalculator(feeCfg.FeeDimensionWeights, nextGasPrice, nextGasCap))
pChainExportFee, err = feeCalc.CalculateFee(tx)
require.NoError(err)
})
Expand Down
88 changes: 56 additions & 32 deletions vms/components/fee/calculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,33 @@ var errGasBoundBreached = errors.New("gas bound breached")
// Calculator performs fee-related operations that are share move P-chain and X-chain
// Calculator is supposed to be embedded with chain specific calculators.
type Calculator struct {
// feeWeights help consolidating complexity into gas
feeWeights Dimensions

// gas cap enforced with adding gas via CumulateGas
gasCap Gas

// Avax denominated gas price, i.e. fee per unit of complexity.
gasPrice GasPrice

// blockGas helps aggregating the gas consumed in a single block
// cumulatedGas helps aggregating the gas consumed in a single block
// so that we can verify it's not too big/build it properly.
blockGas Gas
cumulatedGas Gas

// latestTxComplexity tracks complexity of latest tx being processed.
// latestTxComplexity is especially helpful while building a tx.
latestTxComplexity Dimensions

// currentExcessGas stores current excess gas, cumulated over time
// to be updated once a block is accepted with cumulatedGas
currentExcessGas Gas
}

func NewCalculator(gasPrice GasPrice, gasCap Gas) *Calculator {
func NewCalculator(feeWeights Dimensions, gasPrice GasPrice, gasCap Gas) *Calculator {
return &Calculator{
gasCap: gasCap,
gasPrice: gasPrice,
feeWeights: feeWeights,
gasCap: gasCap,
gasPrice: gasPrice,
}
}

Expand All @@ -46,6 +54,7 @@ func NewUpdatedManager(
parentBlkTime, childBlkTime time.Time,
) (*Calculator, error) {
res := &Calculator{
feeWeights: feesConfig.FeeDimensionWeights,
gasCap: gasCap,
currentExcessGas: currentExcessGas,
}
Expand Down Expand Up @@ -82,63 +91,78 @@ func (c *Calculator) GetGasPrice() GasPrice {
return c.gasPrice
}

func (c *Calculator) GetBlockGas() Gas {
return c.blockGas
func (c *Calculator) GetBlockGas() (Gas, error) {
txGas, err := ToGas(c.feeWeights, c.latestTxComplexity)
if err != nil {
return ZeroGas, err
}
return c.cumulatedGas + txGas, nil
}

func (c *Calculator) GetGasCap() Gas {
return c.gasCap
}

func (c *Calculator) GetExcessGas() Gas {
return c.currentExcessGas
}

// CalculateFee must be a stateless method
func (c *Calculator) CalculateFee(g Gas) (uint64, error) {
return safemath.Mul64(uint64(c.gasPrice), uint64(g))
func (c *Calculator) GetExcessGas() (Gas, error) {
g, err := safemath.Add64(uint64(c.currentExcessGas), uint64(c.cumulatedGas))
if err != nil {
return ZeroGas, err
}
return Gas(g), nil
}

// CumulateGas tries to cumulate the consumed gas [units]. Before
// CumulateComplexity tries to cumulate the consumed gas [units]. Before
// actually cumulating it, it checks whether the result would breach [bounds].
// If so, it returns the first dimension to breach bounds.
func (c *Calculator) CumulateGas(gas Gas) error {
func (c *Calculator) CumulateComplexity(complexity Dimensions) error {
// Ensure we can consume (don't want partial update of values)
blkGas, err := safemath.Add64(uint64(c.blockGas), uint64(gas))
uc, err := Add(c.latestTxComplexity, complexity)
if err != nil {
return fmt.Errorf("%w: %w", errGasBoundBreached, err)
}
if Gas(blkGas) > c.gasCap {
return errGasBoundBreached
}

excessGas, err := safemath.Add64(uint64(c.currentExcessGas), uint64(gas))
totalGas, err := c.GetBlockGas()
if err != nil {
return fmt.Errorf("%w: %w", errGasBoundBreached, err)
}
if totalGas > c.gasCap {
return fmt.Errorf("%w: %w", errGasBoundBreached, err)
}

c.blockGas = Gas(blkGas)
c.currentExcessGas = Gas(excessGas)
c.latestTxComplexity = uc
return nil
}

// Sometimes, e.g. while building a tx, we'd like freedom to speculatively add complexity
// and to remove it later on. [RemoveGas] grants this freedom
func (c *Calculator) RemoveGas(gasToRm Gas) error {
rBlkdGas, err := safemath.Sub(c.blockGas, gasToRm)
func (c *Calculator) RemoveComplexity(complexity Dimensions) error {
rc, err := Remove(c.latestTxComplexity, complexity)
if err != nil {
return fmt.Errorf("%w: current Gas %d, gas to revert %d", err, c.blockGas, gasToRm)
return fmt.Errorf("%w: current Gas %d, gas to revert %d", err, c.cumulatedGas, complexity)
}
rExcessGas, err := safemath.Sub(c.currentExcessGas, gasToRm)
c.latestTxComplexity = rc
return nil
}

// DoneWithLatestTx should be invoked one a tx has been fully processed, before moving to the next one
func (c *Calculator) DoneWithLatestTx() error {
txGas, err := ToGas(c.feeWeights, c.latestTxComplexity)
if err != nil {
return fmt.Errorf("%w: current Excess gas %d, gas to revert %d", err, c.currentExcessGas, gasToRm)
return err
}

c.blockGas = rBlkdGas
c.currentExcessGas = rExcessGas
c.cumulatedGas += txGas
c.latestTxComplexity = Empty
return nil
}

// CalculateFee must be a stateless method
func (c *Calculator) GetLatestTxFee() (uint64, error) {
gas, err := ToGas(c.feeWeights, c.latestTxComplexity)
if err != nil {
return 0, err
}
return safemath.Mul64(uint64(c.gasPrice), uint64(gas))
}

// fakeExponential approximates factor * e ** (numerator / denominator) using
// Taylor expansion.
func fakeExponential(f GasPrice, n, d Gas) GasPrice {
Expand Down
16 changes: 14 additions & 2 deletions vms/components/fee/dimensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,20 @@ func Add(lhs, rhs Dimensions) (Dimensions, error) {
return res, nil
}

func Remove(lhs, rhs Dimensions) (Dimensions, error) {
var res Dimensions
for i := 0; i < FeeDimensions; i++ {
v, err := safemath.Sub(lhs[i], rhs[i])
if err != nil {
return res, err
}
res[i] = v
}
return res, nil
}

func ToGas(weights, dimensions Dimensions) (Gas, error) {
var res uint64
res := uint64(0)
for i := 0; i < FeeDimensions; i++ {
v, err := safemath.Mul64(weights[i], dimensions[i])
if err != nil {
Expand All @@ -54,5 +66,5 @@ func ToGas(weights, dimensions Dimensions) (Gas, error) {
return ZeroGas, err
}
}
return Gas(res), nil
return Gas(res) / 10, nil
}
7 changes: 3 additions & 4 deletions vms/components/fee/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,11 @@ func TestPChainGasPriceIncreaseDueToPeak(t *testing.T) {
)

// at peak the total fee should be no more than 100 Avax.
childGas, err := ToGas(testDynamicFeeCfg.FeeDimensionWeights, childBlkData.complexity)
require.NoError(err)

fee, err := m.CalculateFee(childGas)
require.NoError(m.CumulateComplexity(childBlkData.complexity))
fee, err := m.GetLatestTxFee()
require.NoError(err)
require.Less(fee, 100*units.Avax, fmt.Sprintf("iteration: %d, total: %d", i, len(blockComplexities)))
require.NoError(m.DoneWithLatestTx())

peakGasPrice = m.GetGasPrice()
}
Expand Down
16 changes: 12 additions & 4 deletions vms/platformvm/block/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,10 +375,18 @@ func packBlockTxs(

// pre e upgrade is active, we fill blocks till a target size
// post e upgrade is active, we fill blocks till a target gas
targetSizeReached := (!isEActive && txSize > remainingSize) ||
(isEActive && feeCalculator.GetBlockGas() >= gasCap)
if targetSizeReached {
break
if !isEActive {
if txSize > remainingSize {
break
}
} else {
blkGas, err := feeCalculator.GetBlockGas()
if err != nil {
return nil, err
}
if blkGas >= gasCap {
break
}
}
mempool.Remove(tx)

Expand Down
30 changes: 25 additions & 5 deletions vms/platformvm/block/executor/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,15 @@ func (v *verifier) proposalBlock(
return err
}

blkGas := feeCalculator.GetBlockGas()
blkGas, err := feeCalculator.GetBlockGas()
if err != nil {
return err
}
excessGas, err := feeCalculator.GetExcessGas()
if err != nil {
return err
}

if feeCalculator.IsEActive() {
nextGasCap := commonfee.UpdateGasCap(currentGasCap, blkGas)
onCommitState.SetCurrentGasCap(nextGasCap)
Expand Down Expand Up @@ -451,7 +459,7 @@ func (v *verifier) proposalBlock(
// always be the same as the Banff Proposal Block.
timestamp: onAbortState.GetTimestamp(),
blockGas: blkGas,
excessGas: feeCalculator.GetExcessGas(),
excessGas: excessGas,
atomicRequests: atomicRequests,
}
return nil
Expand All @@ -477,7 +485,15 @@ func (v *verifier) standardBlock(

blkID := b.ID()

blkGas := feeCalculator.GetBlockGas()
blkGas, err := feeCalculator.GetBlockGas()
if err != nil {
return err
}
excessGas, err := feeCalculator.GetExcessGas()
if err != nil {
return err
}

if feeCalculator.IsEActive() {
nextGasCap := commonfee.UpdateGasCap(currentGasCap, blkGas)
onAcceptState.SetCurrentGasCap(nextGasCap)
Expand All @@ -491,7 +507,7 @@ func (v *verifier) standardBlock(

timestamp: onAcceptState.GetTimestamp(),
blockGas: blkGas,
excessGas: feeCalculator.GetExcessGas(),
excessGas: excessGas,
inputs: inputs,
atomicRequests: atomicRequests,
}
Expand Down Expand Up @@ -558,7 +574,11 @@ func (v *verifier) processStandardTxs(
}

if v.txExecutorBackend.Config.UpgradeConfig.IsEActivated(state.GetTimestamp()) {
state.SetExcessGas(feeCalculator.GetExcessGas())
excessGas, err := feeCalculator.GetExcessGas()
if err != nil {
return nil, nil, nil, err
}
state.SetExcessGas(excessGas)
}

if numFuncs := len(funcs); numFuncs == 1 {
Expand Down
9 changes: 9 additions & 0 deletions vms/platformvm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ type Client interface {
GetBlock(ctx context.Context, blockID ids.ID, options ...rpc.Option) ([]byte, error)
// GetBlockByHeight returns the block at the given [height].
GetBlockByHeight(ctx context.Context, height uint64, options ...rpc.Option) ([]byte, error)

// GetDynamicFeeConfig returns DynamicFeesConfig
GetDynamicFeeConfig(ctx context.Context, options ...rpc.Option) (commonfee.DynamicFeesConfig, error)
// GetNextGasData returns the gas price that a transaction must pay to be accepted now
// and the gas cap, i.e. the maximum gas a transactions can consume
GetNextGasData(ctx context.Context, options ...rpc.Option) (commonfee.GasPrice, commonfee.Gas, error)
Expand Down Expand Up @@ -549,6 +552,12 @@ func AwaitTxAccepted(
}
}

func (c *client) GetDynamicFeeConfig(ctx context.Context, options ...rpc.Option) (commonfee.DynamicFeesConfig, error) {
res := &DynamicFeesConfigReply{}
err := c.requester.SendRequest(ctx, "platform.getDynamicFeeConfig", struct{}{}, res, options...)
return res.DynamicFeesConfig, err
}

func (c *client) GetNextGasData(ctx context.Context, options ...rpc.Option) (commonfee.GasPrice, commonfee.Gas, error) {
res := &GetGasPriceReply{}
err := c.requester.SendRequest(ctx, "platform.getNextGasData", struct{}{}, res, options...)
Expand Down
31 changes: 31 additions & 0 deletions vms/platformvm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/state"
"github.com/ava-labs/avalanchego/vms/platformvm/status"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/platformvm/txs/fee"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"

avajson "github.com/ava-labs/avalanchego/utils/json"
Expand Down Expand Up @@ -1829,6 +1830,36 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr
return err
}

// DynamicFeesConfigReply is the response from GetDynamicFeeConfig
type DynamicFeesConfigReply struct {
commonfee.DynamicFeesConfig `json:"nextGasPrice"`
}

// GetNextFeeRates returns the next fee rates that a transaction must pay to be accepted now
func (s *Service) GetDynamicFeeConfig(_ *http.Request, _ *struct{}, reply *DynamicFeesConfigReply) error {
s.vm.ctx.Log.Debug("API called",
zap.String("service", "platform"),
zap.String("method", "getDynamicFeeConfig"),
)

s.vm.ctx.Lock.Lock()
defer s.vm.ctx.Lock.Unlock()

chainTime := s.vm.state.GetTimestamp()
isEActive := s.vm.Config.UpgradeConfig.IsEActivated(chainTime)
cfg, err := fee.GetDynamicConfig(isEActive)
switch err {
case fee.ErrDynamicFeeConfigNotAvailable:
reply.DynamicFeesConfig = commonfee.DynamicFeesConfig{}
return nil
case nil:
reply.DynamicFeesConfig = cfg
return nil
default:
return err
}
}

// GetGasPriceReply is the response from GetFeeRates
type GetGasPriceReply struct {
NextGasPrice commonfee.GasPrice `json:"nextGasPrice"`
Expand Down
2 changes: 1 addition & 1 deletion vms/platformvm/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,7 @@ func TestGetFeeRates(t *testing.T) {

// let time tick. Fee rates will go down
service.vm.ctx.Lock.Lock()
now = now.Add(3 * time.Second)
now = now.Add(10 * time.Second)
service.vm.clock.Set(now)
service.vm.ctx.Lock.Unlock()

Expand Down
Loading