Skip to content

Commit

Permalink
Add P-chain fee APIs (#3286)
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph authored Aug 15, 2024
1 parent aececb0 commit 9a9244f
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 13 deletions.
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
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 {
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
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(
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
65 changes: 65 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,67 @@ 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 FuzzGetFeeState(f *testing.F) {
f.Fuzz(func(t *testing.T, capacity, excess uint64) {
require := require.New(t)

service, _, _ := defaultService(t)

var (
expectedState = fee.State{
Capacity: fee.Gas(capacity),
Excess: fee.Gas(excess),
}
expectedTime = time.Now()
expectedReply = GetFeeStateReply{
State: expectedState,
Price: defaultDynamicFeeConfig.MinGasPrice.MulExp(
expectedState.Excess,
defaultDynamicFeeConfig.ExcessConversionConstant,
),
Time: expectedTime,
}
)

service.vm.ctx.Lock.Lock()
service.vm.state.SetFeeState(expectedState)
service.vm.state.SetTimestamp(expectedTime)
service.vm.ctx.Lock.Unlock()

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

0 comments on commit 9a9244f

Please sign in to comment.