diff --git a/.github/workflows/build-macos-release.yml b/.github/workflows/build-macos-release.yml index 62856e8131da..b37d1dc01545 100644 --- a/.github/workflows/build-macos-release.yml +++ b/.github/workflows/build-macos-release.yml @@ -18,7 +18,7 @@ jobs: # This workflow contains a single job called "build" build-mac: # The type of runner that the job will run on - runs-on: macos-12 + runs-on: macos-14 permissions: id-token: write contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0851d0cf44a..4a489b3c16c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - os: [macos-12, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, windows-2022, custom-arm64-jammy, custom-arm64-noble] + os: [macos-14, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, windows-2022, custom-arm64-jammy, custom-arm64-noble] steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-go-for-project diff --git a/config/config.go b/config/config.go index 46fd0aac8af5..8a95045b7f77 100644 --- a/config/config.go +++ b/config/config.go @@ -793,8 +793,8 @@ func getTxFeeConfig(v *viper.Viper, networkID uint32) genesis.TxFeeConfig { MinPrice: gas.Price(v.GetUint64(DynamicFeesMinGasPriceKey)), ExcessConversionConstant: gas.Gas(v.GetUint64(DynamicFeesExcessConversionConstantKey)), }, - ValidatorFeeCapacity: gas.Gas(v.GetUint64(ValidatorFeesCapacityKey)), ValidatorFeeConfig: validatorfee.Config{ + Capacity: gas.Gas(v.GetUint64(ValidatorFeesCapacityKey)), Target: gas.Gas(v.GetUint64(ValidatorFeesTargetKey)), MinPrice: gas.Price(v.GetUint64(ValidatorFeesMinPriceKey)), ExcessConversionConstant: gas.Gas(v.GetUint64(ValidatorFeesExcessConversionConstantKey)), diff --git a/config/flags.go b/config/flags.go index c20b243c8079..d8397ce40a2c 100644 --- a/config/flags.go +++ b/config/flags.go @@ -106,7 +106,7 @@ func addNodeFlags(fs *pflag.FlagSet) { // AVAX fees: // Validator fees: - fs.Uint64(ValidatorFeesCapacityKey, uint64(genesis.LocalParams.ValidatorFeeCapacity), "Maximum number of validators") + fs.Uint64(ValidatorFeesCapacityKey, uint64(genesis.LocalParams.ValidatorFeeConfig.Capacity), "Maximum number of validators") fs.Uint64(ValidatorFeesTargetKey, uint64(genesis.LocalParams.ValidatorFeeConfig.Target), "Target number of validators") fs.Uint64(ValidatorFeesMinPriceKey, uint64(genesis.LocalParams.ValidatorFeeConfig.MinPrice), "Minimum validator price in nAVAX per second") fs.Uint64(ValidatorFeesExcessConversionConstantKey, uint64(genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant), "Constant to convert validator excess price") diff --git a/genesis/genesis_fuji.go b/genesis/genesis_fuji.go index a6b4c4ca960a..7aa2195c5fcc 100644 --- a/genesis/genesis_fuji.go +++ b/genesis/genesis_fuji.go @@ -52,8 +52,8 @@ var ( // hardcode the result. ExcessConversionConstant: 5_410_106, // Double every 30s }, - ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ + Capacity: 20_000, Target: 10_000, MinPrice: gas.Price(512 * units.NanoAvax), // ExcessConversionConstant = (Capacity - Target) * NumberOfSecondsPerDoubling / ln(2) diff --git a/genesis/genesis_local.go b/genesis/genesis_local.go index 166229508f10..fe9aac873047 100644 --- a/genesis/genesis_local.go +++ b/genesis/genesis_local.go @@ -70,8 +70,8 @@ var ( // hardcode the result. ExcessConversionConstant: 5_410_106, // Double every 30s }, - ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ + Capacity: 20_000, Target: 10_000, MinPrice: gas.Price(1 * units.NanoAvax), // ExcessConversionConstant = (Capacity - Target) * NumberOfSecondsPerDoubling / ln(2) diff --git a/genesis/genesis_mainnet.go b/genesis/genesis_mainnet.go index dd5b61d481da..2ec03626ebf1 100644 --- a/genesis/genesis_mainnet.go +++ b/genesis/genesis_mainnet.go @@ -52,8 +52,8 @@ var ( // hardcode the result. ExcessConversionConstant: 5_410_106, // Double every 30s }, - ValidatorFeeCapacity: 20_000, ValidatorFeeConfig: validatorfee.Config{ + Capacity: 20_000, Target: 10_000, MinPrice: gas.Price(512 * units.NanoAvax), // ExcessConversionConstant = (Capacity - Target) * NumberOfSecondsPerDoubling / ln(2) diff --git a/genesis/params.go b/genesis/params.go index 43c2f1c1301c..c591c9ed67d8 100644 --- a/genesis/params.go +++ b/genesis/params.go @@ -38,11 +38,10 @@ type StakingConfig struct { } type TxFeeConfig struct { - CreateAssetTxFee uint64 `json:"createAssetTxFee"` - StaticFeeConfig txfee.StaticConfig `json:"staticFeeConfig"` - DynamicFeeConfig gas.Config `json:"dynamicFeeConfig"` - ValidatorFeeCapacity gas.Gas `json:"validatorFeeCapacity"` - ValidatorFeeConfig validatorfee.Config `json:"validatorFeeConfig"` + CreateAssetTxFee uint64 `json:"createAssetTxFee"` + StaticFeeConfig txfee.StaticConfig `json:"staticFeeConfig"` + DynamicFeeConfig gas.Config `json:"dynamicFeeConfig"` + ValidatorFeeConfig validatorfee.Config `json:"validatorFeeConfig"` } type Params struct { diff --git a/network/p2p/acp118/handler.go b/network/p2p/acp118/handler.go index 706476772598..f9d097ce39ec 100644 --- a/network/p2p/acp118/handler.go +++ b/network/p2p/acp118/handler.go @@ -10,6 +10,7 @@ import ( "google.golang.org/protobuf/proto" + "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/proto/pb/sdk" @@ -32,7 +33,22 @@ type Verifier interface { // NewHandler returns an instance of Handler func NewHandler(verifier Verifier, signer warp.Signer) *Handler { + return NewCachedHandler( + &cache.Empty[ids.ID, []byte]{}, + verifier, + signer, + ) +} + +// NewCachedHandler returns an instance of Handler that caches successful +// requests. +func NewCachedHandler( + cacher cache.Cacher[ids.ID, []byte], + verifier Verifier, + signer warp.Signer, +) *Handler { return &Handler{ + cacher: cacher, verifier: verifier, signer: signer, } @@ -42,6 +58,7 @@ func NewHandler(verifier Verifier, signer warp.Signer) *Handler { type Handler struct { p2p.NoOpHandler + cacher cache.Cacher[ids.ID, []byte] verifier Verifier signer warp.Signer } @@ -68,6 +85,11 @@ func (h *Handler) AppRequest( } } + msgID := msg.ID() + if responseBytes, ok := h.cacher.Get(msgID); ok { + return responseBytes, nil + } + if err := h.verifier.Verify(ctx, msg, request.Justification); err != nil { return nil, err } @@ -92,5 +114,6 @@ func (h *Handler) AppRequest( } } + h.cacher.Put(msgID, responseBytes) return responseBytes, nil } diff --git a/network/p2p/acp118/handler_test.go b/network/p2p/acp118/handler_test.go index 4ca7add318da..081ecd1f0351 100644 --- a/network/p2p/acp118/handler_test.go +++ b/network/p2p/acp118/handler_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" + "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/p2ptest" "github.com/ava-labs/avalanchego/proto/pb/sdk" @@ -23,20 +24,46 @@ var _ Verifier = (*testVerifier)(nil) func TestHandler(t *testing.T) { tests := []struct { - name string - verifier Verifier - expectedErr error - expectedVerify bool + name string + cacher cache.Cacher[ids.ID, []byte] + verifier Verifier + expectedErrs []error }{ { - name: "signature fails verification", - verifier: &testVerifier{Err: &common.AppError{Code: 123}}, - expectedErr: &common.AppError{Code: 123}, + name: "signature fails verification", + cacher: &cache.Empty[ids.ID, []byte]{}, + verifier: &testVerifier{ + Errs: []*common.AppError{ + {Code: 123}, + }, + }, + expectedErrs: []error{ + &common.AppError{Code: 123}, + }, }, { - name: "signature signed", - verifier: &testVerifier{}, - expectedVerify: true, + name: "signature signed", + cacher: &cache.Empty[ids.ID, []byte]{}, + verifier: &testVerifier{}, + expectedErrs: []error{ + nil, + }, + }, + { + name: "signature is cached", + cacher: &cache.LRU[ids.ID, []byte]{ + Size: 1, + }, + verifier: &testVerifier{ + Errs: []*common.AppError{ + nil, + {Code: 123}, // The valid response should be cached + }, + }, + expectedErrs: []error{ + nil, + nil, + }, }, } @@ -51,7 +78,7 @@ func TestHandler(t *testing.T) { networkID := uint32(123) chainID := ids.GenerateTestID() signer := warp.NewSigner(sk, networkID, chainID) - h := NewHandler(tt.verifier, signer) + h := NewCachedHandler(tt.cacher, tt.verifier, signer) clientNodeID := ids.GenerateTestNodeID() serverNodeID := ids.GenerateTestNodeID() c := p2ptest.NewClient( @@ -77,12 +104,17 @@ func TestHandler(t *testing.T) { requestBytes, err := proto.Marshal(request) require.NoError(err) - done := make(chan struct{}) + var ( + expectedErr error + handled = make(chan struct{}) + ) onResponse := func(_ context.Context, _ ids.NodeID, responseBytes []byte, appErr error) { - defer close(done) + defer func() { + handled <- struct{}{} + }() + require.ErrorIs(appErr, expectedErr) if appErr != nil { - require.ErrorIs(tt.expectedErr, appErr) return } @@ -92,24 +124,31 @@ func TestHandler(t *testing.T) { signature, err := bls.SignatureFromBytes(response.Signature) require.NoError(err) - require.Equal(tt.expectedVerify, bls.Verify(pk, signature, request.Message)) + require.True(bls.Verify(pk, signature, request.Message)) } - require.NoError(c.AppRequest(ctx, set.Of(clientNodeID), requestBytes, onResponse)) - <-done + for _, expectedErr = range tt.expectedErrs { + require.NoError(c.AppRequest(ctx, set.Of(clientNodeID), requestBytes, onResponse)) + <-handled + } }) } } // The zero value of testVerifier allows signing type testVerifier struct { - Err *common.AppError + Errs []*common.AppError } -func (t testVerifier) Verify( +func (t *testVerifier) Verify( context.Context, *warp.UnsignedMessage, []byte, ) *common.AppError { - return t.Err + if len(t.Errs) == 0 { + return nil + } + err := t.Errs[0] + t.Errs = t.Errs[1:] + return err } diff --git a/network/p2p/router.go b/network/p2p/router.go index 9c020ab49356..dba1bd124d19 100644 --- a/network/p2p/router.go +++ b/network/p2p/router.go @@ -33,12 +33,6 @@ type pendingAppRequest struct { callback AppResponseCallback } -// meteredHandler emits metrics for a Handler -type meteredHandler struct { - *responder - metrics -} - type metrics struct { msgTime *prometheus.GaugeVec msgCount *prometheus.CounterVec @@ -69,7 +63,7 @@ type router struct { metrics metrics lock sync.RWMutex - handlers map[uint64]*meteredHandler + handlers map[uint64]*responder pendingAppRequests map[uint32]pendingAppRequest requestID uint32 } @@ -84,7 +78,7 @@ func newRouter( log: log, sender: sender, metrics: metrics, - handlers: make(map[uint64]*meteredHandler), + handlers: make(map[uint64]*responder), pendingAppRequests: make(map[uint32]pendingAppRequest), // invariant: sdk uses odd-numbered requestIDs requestID: 1, @@ -99,14 +93,11 @@ func (r *router) addHandler(handlerID uint64, handler Handler) error { return fmt.Errorf("failed to register handler id %d: %w", handlerID, ErrExistingAppProtocol) } - r.handlers[handlerID] = &meteredHandler{ - responder: &responder{ - Handler: handler, - handlerID: handlerID, - log: r.log, - sender: r.sender, - }, - metrics: r.metrics, + r.handlers[handlerID] = &responder{ + Handler: handler, + handlerID: handlerID, + log: r.log, + sender: r.sender, } return nil @@ -235,7 +226,7 @@ func (r *router) AppGossip(ctx context.Context, nodeID ids.NodeID, gossip []byte // - A boolean indicating that parsing succeeded. // // Invariant: Assumes [r.lock] isn't held. -func (r *router) parse(prefixedMsg []byte) ([]byte, *meteredHandler, string, bool) { +func (r *router) parse(prefixedMsg []byte) ([]byte, *responder, string, bool) { handlerID, msg, ok := ParseMessage(prefixedMsg) if !ok { return nil, nil, "", false diff --git a/node/node.go b/node/node.go index 3eb2f9b18447..1fedf35eb97e 100644 --- a/node/node.go +++ b/node/node.go @@ -1217,7 +1217,6 @@ func (n *Node) initVMs() error { CreateAssetTxFee: n.Config.CreateAssetTxFee, StaticFeeConfig: n.Config.StaticFeeConfig, DynamicFeeConfig: n.Config.DynamicFeeConfig, - ValidatorFeeCapacity: n.Config.ValidatorFeeCapacity, ValidatorFeeConfig: n.Config.ValidatorFeeConfig, UptimePercentage: n.Config.UptimeRequirement, MinValidatorStake: n.Config.MinValidatorStake, diff --git a/tests/fixture/bootstrapmonitor/e2e/e2e_test.go b/tests/fixture/bootstrapmonitor/e2e/e2e_test.go index 2f5a319fc02d..45de49bce990 100644 --- a/tests/fixture/bootstrapmonitor/e2e/e2e_test.go +++ b/tests/fixture/bootstrapmonitor/e2e/e2e_test.go @@ -234,7 +234,10 @@ func buildImage(tc tests.TestContext, imageName string, forceNewHash bool, scrip repoRoot, err := e2e.GetRepoRootPath(repoRelativePath) require.NoError(err) - var args []string + args := []string{ + "-x", // Ensure script output to aid in debugging + filepath.Join(repoRoot, "scripts", scriptName), + } if forceNewHash { // Ensure the build results in a new image hash by preventing use of a cached final stage args = append(args, "--no-cache-filter", "execution") @@ -242,7 +245,7 @@ func buildImage(tc tests.TestContext, imageName string, forceNewHash bool, scrip cmd := exec.CommandContext( tc.DefaultContext(), - filepath.Join(repoRoot, "scripts", scriptName), + "bash", args..., ) // #nosec G204 cmd.Env = append(os.Environ(), diff --git a/vms/platformvm/config/config.go b/vms/platformvm/config/config.go index f8bda923455c..897f910a1380 100644 --- a/vms/platformvm/config/config.go +++ b/vms/platformvm/config/config.go @@ -42,8 +42,7 @@ type Config struct { DynamicFeeConfig gas.Config // ACP-77 validator fees are active after Etna - ValidatorFeeCapacity gas.Gas - ValidatorFeeConfig validatorfee.Config + ValidatorFeeConfig validatorfee.Config // Provides access to the uptime manager as a thread safe data structure UptimeLockedCalculator uptime.LockedCalculator diff --git a/vms/platformvm/txs/executor/standard_tx_executor.go b/vms/platformvm/txs/executor/standard_tx_executor.go index a94d25d2d294..edf5babed0fb 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor.go +++ b/vms/platformvm/txs/executor/standard_tx_executor.go @@ -586,7 +586,7 @@ func (e *StandardTxExecutor) ConvertSubnetTx(tx *txs.ConvertSubnetTx) error { } if vdr.Balance != 0 { // We are attempting to add an active validator - if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } @@ -772,7 +772,7 @@ func (e *StandardTxExecutor) RegisterSubnetValidatorTx(tx *txs.RegisterSubnetVal } if tx.Balance != 0 { // We are attempting to add an active validator - if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeCapacity { + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeConfig.Capacity { return errMaxNumActiveValidators } @@ -981,6 +981,10 @@ func (e *StandardTxExecutor) IncreaseBalanceTx(tx *txs.IncreaseBalanceTx) error // If the validator is currently inactive, we are activating it. if sov.EndAccumulatedFee == 0 { + if gas.Gas(e.State.NumActiveSubnetOnlyValidators()) >= e.Backend.Config.ValidatorFeeConfig.Capacity { + return errMaxNumActiveValidators + } + sov.EndAccumulatedFee = e.State.GetAccruedFees() } sov.EndAccumulatedFee, err = safemath.Add(sov.EndAccumulatedFee, tx.Balance) diff --git a/vms/platformvm/txs/executor/standard_tx_executor_test.go b/vms/platformvm/txs/executor/standard_tx_executor_test.go index 9854ce651157..09af4d56c0db 100644 --- a/vms/platformvm/txs/executor/standard_tx_executor_test.go +++ b/vms/platformvm/txs/executor/standard_tx_executor_test.go @@ -37,13 +37,15 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/state/statetest" "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/platformvm/utxo" "github.com/ava-labs/avalanchego/vms/platformvm/utxo/utxomock" "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" + + txfee "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" + validatorfee "github.com/ava-labs/avalanchego/vms/platformvm/validators/fee" ) // This tests that the math performed during TransformSubnetTx execution can @@ -2388,10 +2390,9 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { var ( ctx = snowtest.Context(t, constants.PlatformChainID) defaultConfig = &config.Config{ - DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, - ValidatorFeeCapacity: genesis.LocalParams.ValidatorFeeCapacity, - ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, - UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), } baseState = statetest.New(t, statetest.Config{ Upgrades: defaultConfig.UpgradeConfig, @@ -2505,19 +2506,23 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "invalid fee calculation", updateExecutor: func(e *StandardTxExecutor) error { - e.FeeCalculator = fee.NewStaticCalculator(e.Config.StaticFeeConfig) + e.FeeCalculator = txfee.NewStaticCalculator(e.Config.StaticFeeConfig) return nil }, - expectedErr: fee.ErrUnsupportedTx, + expectedErr: txfee.ErrUnsupportedTx, }, { name: "too many active validators", updateExecutor: func(e *StandardTxExecutor) error { e.Backend.Config = &config.Config{ - DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, - ValidatorFeeCapacity: 0, - ValidatorFeeConfig: genesis.LocalParams.ValidatorFeeConfig, - UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), + DynamicFeeConfig: genesis.LocalParams.DynamicFeeConfig, + ValidatorFeeConfig: validatorfee.Config{ + Capacity: 0, + Target: genesis.LocalParams.ValidatorFeeConfig.Target, + MinPrice: genesis.LocalParams.ValidatorFeeConfig.MinPrice, + ExcessConversionConstant: genesis.LocalParams.ValidatorFeeConfig.ExcessConversionConstant, + }, + UpgradeConfig: upgradetest.GetConfig(upgradetest.Latest), } return nil }, @@ -2538,7 +2543,7 @@ func TestStandardExecutorConvertSubnetTx(t *testing.T) { { name: "insufficient fee", updateExecutor: func(e *StandardTxExecutor) error { - e.FeeCalculator = fee.NewDynamicCalculator( + e.FeeCalculator = txfee.NewDynamicCalculator( e.Config.DynamicFeeConfig.Weights, 100*genesis.LocalParams.DynamicFeeConfig.MinPrice, ) diff --git a/vms/platformvm/validators/fee/fee.go b/vms/platformvm/validators/fee/fee.go index 23705d8b25a2..df466a70770f 100644 --- a/vms/platformvm/validators/fee/fee.go +++ b/vms/platformvm/validators/fee/fee.go @@ -13,6 +13,7 @@ import ( // Config contains all the static parameters of the dynamic fee mechanism. type Config struct { + Capacity gas.Gas `json:"capacity"` Target gas.Gas `json:"target"` MinPrice gas.Price `json:"minPrice"` ExcessConversionConstant gas.Gas `json:"excessConversionConstant"`