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

Add P-chain fee APIs #3286

Merged
merged 12 commits into from
Aug 15, 2024
17 changes: 17 additions & 0 deletions vms/platformvm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/rpc"
"github.com/ava-labs/avalanchego/vms/components/fee"
"github.com/ava-labs/avalanchego/vms/platformvm/fx"
"github.com/ava-labs/avalanchego/vms/platformvm/status"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
Expand Down Expand Up @@ -123,6 +124,10 @@ 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)
// GetFeeConfig returns the dynamic fee config of the chain.
GetFeeConfig(ctx context.Context, options ...rpc.Option) (*fee.Config, error)
// GetFeeState returns the current fee state of the chain.
GetFeeState(ctx context.Context, options ...rpc.Option) (fee.State, fee.GasPrice, time.Time, error)
}

// Client implementation for interacting with the P Chain endpoint
Expand Down Expand Up @@ -517,6 +522,18 @@ func (c *client) GetBlockByHeight(ctx context.Context, height uint64, options ..
return formatting.Decode(res.Encoding, res.Block)
}

func (c *client) GetFeeConfig(ctx context.Context, options ...rpc.Option) (*fee.Config, error) {
res := &fee.Config{}
err := c.requester.SendRequest(ctx, "platform.getFeeConfig", struct{}{}, res, options...)
return res, err
}

func (c *client) GetFeeState(ctx context.Context, options ...rpc.Option) (fee.State, fee.GasPrice, time.Time, error) {
res := &GetFeeStateReply{}
err := c.requester.SendRequest(ctx, "platform.getFeeState", struct{}{}, res, options...)
return res.State, res.Price, res.Time, err
}

func AwaitTxAccepted(
c Client,
ctx context.Context,
Expand Down
43 changes: 43 additions & 0 deletions vms/platformvm/service.go
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/components/fee"
"github.com/ava-labs/avalanchego/vms/components/keystore"
"github.com/ava-labs/avalanchego/vms/platformvm/fx"
"github.com/ava-labs/avalanchego/vms/platformvm/reward"
Expand Down Expand Up @@ -1828,6 +1829,48 @@ func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightAr
return err
}

// GetFeeConfig returns the dynamic fee config of the chain.
func (s *Service) GetFeeConfig(_ *http.Request, _ *struct{}, reply *fee.Config) error {
ARR4N marked this conversation as resolved.
Show resolved Hide resolved
s.vm.ctx.Log.Debug("API called",
zap.String("service", "platform"),
zap.String("method", "getFeeConfig"),
)

// TODO: Remove after Etna is activated.
now := time.Now()
if !s.vm.Config.UpgradeConfig.IsEtnaActivated(now) {
return nil
}

*reply = s.vm.DynamicFeeConfig
return nil
}

type GetFeeStateReply struct {
fee.State
ARR4N marked this conversation as resolved.
Show resolved Hide resolved
Price fee.GasPrice `json:"price"`
Time time.Time `json:"timestamp"`
}

// GetFeeState returns the current fee state of the chain.
func (s *Service) GetFeeState(_ *http.Request, _ *struct{}, reply *GetFeeStateReply) error {
s.vm.ctx.Log.Debug("API called",
zap.String("service", "platform"),
zap.String("method", "getFeeState"),
)

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

reply.State = s.vm.state.GetFeeState()
reply.Price = s.vm.DynamicFeeConfig.MinGasPrice.MulExp(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there shared code that performs this calculation? I'm guessing the price is used somewhere internally, and this is at risk of diverging.

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'm not sure how better to share this... This function takes in 3 params to calculate the price... Any helper would also take in 3 params and just call this function... No?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will change this in a breakout

reply.State.Excess,
s.vm.DynamicFeeConfig.ExcessConversionConstant,
)
reply.Time = s.vm.state.GetTimestamp()
return nil
}

func (s *Service) getAPIUptime(staker *state.Staker) (*avajson.Float32, error) {
// Only report uptimes that we have been actively tracking.
if constants.PrimaryNetworkID != staker.SubnetID && !s.vm.TrackedSubnets.Contains(staker.SubnetID) {
Expand Down
63 changes: 63 additions & 0 deletions vms/platformvm/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/ava-labs/avalanchego/utils/formatting"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/vms/components/avax"
"github.com/ava-labs/avalanchego/vms/components/fee"
"github.com/ava-labs/avalanchego/vms/platformvm/block"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
"github.com/ava-labs/avalanchego/vms/platformvm/state"
Expand Down Expand Up @@ -1107,3 +1108,65 @@ func TestServiceGetSubnets(t *testing.T) {
},
}, response.Subnets)
}

func TestGetFeeConfig(t *testing.T) {
tests := []struct {
name string
etnaTime time.Time
expected fee.Config
}{
{
name: "pre-etna",
etnaTime: time.Now().Add(time.Hour),
expected: fee.Config{},
},
{
name: "post-etna",
etnaTime: time.Now().Add(-time.Hour),
expected: defaultDynamicFeeConfig,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)

service, _, _ := defaultService(t)
service.vm.Config.UpgradeConfig.EtnaTime = test.etnaTime

var reply fee.Config
require.NoError(service.GetFeeConfig(nil, nil, &reply))
require.Equal(test.expected, reply)
})
}
}

func TestGetFeeState(t *testing.T) {
require := require.New(t)

seed := time.Now().UnixNano()
t.Logf("Seed: %d", seed)
random := rand.New(rand.NewSource(seed)) // #nosec G404
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved

service, _, _ := defaultService(t)

expectedState := fee.State{
Capacity: fee.Gas(random.Uint64()),
Excess: fee.Gas(random.Uint64()),
}
service.vm.state.SetFeeState(expectedState)
timestamp := time.Now()
service.vm.state.SetTimestamp(timestamp)

expectedReply := GetFeeStateReply{
State: expectedState,
Price: defaultDynamicFeeConfig.MinGasPrice.MulExp(
expectedState.Excess,
defaultDynamicFeeConfig.ExcessConversionConstant,
),
Time: timestamp,
}

var reply GetFeeStateReply
require.NoError(service.GetFeeState(nil, nil, &reply))
require.Equal(expectedReply, reply)
}
43 changes: 30 additions & 13 deletions vms/platformvm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ import (
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
"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/platformvm/txs/txstest"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"

Expand All @@ -68,9 +67,11 @@ import (
smeng "github.com/ava-labs/avalanchego/snow/engine/snowman"
snowgetter "github.com/ava-labs/avalanchego/snow/engine/snowman/getter"
timetracker "github.com/ava-labs/avalanchego/snow/networking/tracker"
feecomponent "github.com/ava-labs/avalanchego/vms/components/fee"
blockbuilder "github.com/ava-labs/avalanchego/vms/platformvm/block/builder"
blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor"
txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee"
walletbuilder "github.com/ava-labs/avalanchego/wallet/chain/p/builder"
walletsigner "github.com/ava-labs/avalanchego/wallet/chain/p/signer"
walletcommon "github.com/ava-labs/avalanchego/wallet/subnet/primary/common"
Expand Down Expand Up @@ -123,6 +124,26 @@ var (
defaultMaxValidatorStake = 100 * defaultMinValidatorStake
defaultBalance = 2 * defaultMaxValidatorStake // amount all genesis validators have in defaultVM

defaultStaticFeeConfig = txfee.StaticConfig{
TxFee: defaultTxFee,
CreateSubnetTxFee: 100 * defaultTxFee,
TransformSubnetTxFee: 100 * defaultTxFee,
CreateBlockchainTxFee: 100 * defaultTxFee,
}
defaultDynamicFeeConfig = feecomponent.Config{
Weights: feecomponent.Dimensions{
feecomponent.Bandwidth: 1,
feecomponent.DBRead: 1,
feecomponent.DBWrite: 1,
feecomponent.Compute: 1,
},
MaxGasCapacity: 10_000,
MaxGasPerSecond: 1_000,
TargetGasPerSecond: 500,
MinGasPrice: 1,
ExcessConversionConstant: 5_000,
}

// subnet that exists at genesis in defaultVM
// Its controlKeys are keys[0], keys[1], keys[2]
// Its threshold is 2
Expand Down Expand Up @@ -248,18 +269,14 @@ func defaultVM(t *testing.T, f fork) (*VM, *txstest.WalletFactory, database.Data
UptimeLockedCalculator: uptime.NewLockedCalculator(),
SybilProtectionEnabled: true,
Validators: validators.NewManager(),
StaticFeeConfig: fee.StaticConfig{
TxFee: defaultTxFee,
CreateSubnetTxFee: 100 * defaultTxFee,
TransformSubnetTxFee: 100 * defaultTxFee,
CreateBlockchainTxFee: 100 * defaultTxFee,
},
MinValidatorStake: defaultMinValidatorStake,
MaxValidatorStake: defaultMaxValidatorStake,
MinDelegatorStake: defaultMinDelegatorStake,
MinStakeDuration: defaultMinStakingDuration,
MaxStakeDuration: defaultMaxStakingDuration,
RewardConfig: defaultRewardConfig,
StaticFeeConfig: defaultStaticFeeConfig,
DynamicFeeConfig: defaultDynamicFeeConfig,
MinValidatorStake: defaultMinValidatorStake,
MaxValidatorStake: defaultMaxValidatorStake,
MinDelegatorStake: defaultMinDelegatorStake,
MinStakeDuration: defaultMinStakingDuration,
MaxStakeDuration: defaultMaxStakingDuration,
RewardConfig: defaultRewardConfig,
UpgradeConfig: upgrade.Config{
ApricotPhase3Time: apricotPhase3Time,
ApricotPhase5Time: apricotPhase5Time,
Expand Down
Loading