From 28edc2e8c43bd80e0ebb304f06d4d413ec0197a4 Mon Sep 17 00:00:00 2001 From: ptrus Date: Wed, 18 Mar 2020 16:18:38 +0100 Subject: [PATCH 1/2] go/staking: add ConensusParameters query method --- go/consensus/tendermint/apps/staking/query.go | 5 +++ go/consensus/tendermint/staking/staking.go | 8 ++++ go/staking/api/api.go | 3 ++ go/staking/api/grpc.go | 37 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/go/consensus/tendermint/apps/staking/query.go b/go/consensus/tendermint/apps/staking/query.go index c6481cf9403..f8e5c58af21 100644 --- a/go/consensus/tendermint/apps/staking/query.go +++ b/go/consensus/tendermint/apps/staking/query.go @@ -23,6 +23,7 @@ type Query interface { Delegations(context.Context, signature.PublicKey) (map[signature.PublicKey]*staking.Delegation, error) DebondingDelegations(context.Context, signature.PublicKey) (map[signature.PublicKey][]*staking.DebondingDelegation, error) Genesis(context.Context) (*staking.Genesis, error) + ConsensusParameters(context.Context) (*staking.ConsensusParameters, error) } // QueryFactory is the staking query factory. @@ -105,6 +106,10 @@ func (sq *stakingQuerier) DebondingDelegations(ctx context.Context, id signature return sq.state.DebondingDelegationsFor(id) } +func (sq *stakingQuerier) ConsensusParameters(ctx context.Context) (*staking.ConsensusParameters, error) { + return sq.state.ConsensusParameters() +} + func (app *stakingApplication) QueryFactory() interface{} { return &QueryFactory{app} } diff --git a/go/consensus/tendermint/staking/staking.go b/go/consensus/tendermint/staking/staking.go index b2f8430170b..a247c30b16d 100644 --- a/go/consensus/tendermint/staking/staking.go +++ b/go/consensus/tendermint/staking/staking.go @@ -140,6 +140,14 @@ func (tb *tendermintBackend) StateToGenesis(ctx context.Context, height int64) ( return q.Genesis(ctx) } +func (tb *tendermintBackend) ConsensusParameters(ctx context.Context, height int64) (*api.ConsensusParameters, error) { + q, err := tb.querier.QueryAt(ctx, height) + if err != nil { + return nil, err + } + + return q.ConsensusParameters(ctx) +} func (tb *tendermintBackend) Cleanup() { <-tb.closedCh } diff --git a/go/staking/api/api.go b/go/staking/api/api.go index da380ac1c47..835f0319171 100644 --- a/go/staking/api/api.go +++ b/go/staking/api/api.go @@ -98,6 +98,9 @@ type Backend interface { // StateToGenesis returns the genesis state at specified block height. StateToGenesis(ctx context.Context, height int64) (*Genesis, error) + // Paremeters returns the staking consensus parameters. + ConsensusParameters(ctx context.Context, height int64) (*ConsensusParameters, error) + // WatchTransfers returns a channel that produces a stream of TranserEvent // on all balance transfers. WatchTransfers(ctx context.Context) (<-chan *TransferEvent, pubsub.ClosableSubscription, error) diff --git a/go/staking/api/grpc.go b/go/staking/api/grpc.go index 9137e42e281..600ff7017cf 100644 --- a/go/staking/api/grpc.go +++ b/go/staking/api/grpc.go @@ -33,6 +33,8 @@ var ( methodDebondingDelegations = serviceName.NewMethod("DebondingDelegations", OwnerQuery{}) // methodStateToGenesis is the StateToGenesis method. methodStateToGenesis = serviceName.NewMethod("StateToGenesis", int64(0)) + // methodConsensusParameters is the ConsensusParameters method. + methodConsensusParameters = serviceName.NewMethod("ConsensusParameters", int64(0)) // methodWatchTransfers is the WatchTransfers method. methodWatchTransfers = serviceName.NewMethod("WatchTransfers", nil) @@ -82,6 +84,10 @@ var ( MethodName: methodStateToGenesis.ShortName(), Handler: handlerStateToGenesis, }, + { + MethodName: methodConsensusParameters.ShortName(), + Handler: handlerConsensusParameters, + }, }, Streams: []grpc.StreamDesc{ { @@ -310,6 +316,29 @@ func handlerStateToGenesis( // nolint: golint return interceptor(ctx, height, info, handler) } +func handlerConsensusParameters( // nolint: golint + srv interface{}, + ctx context.Context, + dec func(interface{}) error, + interceptor grpc.UnaryServerInterceptor, +) (interface{}, error) { + var height int64 + if err := dec(&height); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(Backend).ConsensusParameters(ctx, height) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: methodConsensusParameters.FullName(), + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(Backend).ConsensusParameters(ctx, req.(int64)) + } + return interceptor(ctx, height, info, handler) +} + func handlerWatchTransfers(srv interface{}, stream grpc.ServerStream) error { if err := stream.RecvMsg(nil); err != nil { return err @@ -475,6 +504,14 @@ func (c *stakingClient) StateToGenesis(ctx context.Context, height int64) (*Gene return &rsp, nil } +func (c *stakingClient) ConsensusParameters(ctx context.Context, height int64) (*ConsensusParameters, error) { + var rsp ConsensusParameters + if err := c.conn.Invoke(ctx, methodConsensusParameters.FullName(), height, &rsp); err != nil { + return nil, err + } + return &rsp, nil +} + func (c *stakingClient) WatchTransfers(ctx context.Context) (<-chan *TransferEvent, pubsub.ClosableSubscription, error) { ctx, sub := pubsub.NewContextSubscription(ctx) From f0da05ca64b48fe2893bffa752575e48ae4e258b Mon Sep 17 00:00:00 2001 From: ptrus Date: Wed, 18 Mar 2020 16:19:06 +0100 Subject: [PATCH 2/2] go/txsource: commission schedule amendments workload --- .changelog/2766.feature.md | 3 + .../cmd/debug/txsource/workload/commission.go | 391 ++++++++++++++++++ .../cmd/debug/txsource/workload/workload.go | 1 + go/oasis-test-runner/scenario/e2e/txsource.go | 2 + go/staking/api/commission.go | 8 +- .../txsource/staking-genesis.json | 18 +- 6 files changed, 413 insertions(+), 10 deletions(-) create mode 100644 .changelog/2766.feature.md create mode 100644 go/oasis-node/cmd/debug/txsource/workload/commission.go diff --git a/.changelog/2766.feature.md b/.changelog/2766.feature.md new file mode 100644 index 00000000000..06bcaa62515 --- /dev/null +++ b/.changelog/2766.feature.md @@ -0,0 +1,3 @@ +go/txsource: add a commission schedule amendments workload + +The added workload generated commission schedule amendment requests. diff --git a/go/oasis-node/cmd/debug/txsource/workload/commission.go b/go/oasis-node/cmd/debug/txsource/workload/commission.go new file mode 100644 index 00000000000..eaae8377833 --- /dev/null +++ b/go/oasis-node/cmd/debug/txsource/workload/commission.go @@ -0,0 +1,391 @@ +package workload + +import ( + "context" + "fmt" + "math/rand" + "time" + + "google.golang.org/grpc" + + "github.com/oasislabs/oasis-core/go/common/crypto/signature" + memorySigner "github.com/oasislabs/oasis-core/go/common/crypto/signature/signers/memory" + "github.com/oasislabs/oasis-core/go/common/logging" + consensus "github.com/oasislabs/oasis-core/go/consensus/api" + "github.com/oasislabs/oasis-core/go/consensus/api/transaction" + epochtime "github.com/oasislabs/oasis-core/go/epochtime/api" + staking "github.com/oasislabs/oasis-core/go/staking/api" +) + +const ( + // NameCommission is the name of the commission schedule amendements + // workload. + NameCommission = "commission" + + // Max number of rate change intervals between two bound steps. + commissionMaxBoundChangeIntervals = 10 + // Max number of rate change intervals between two rate steps. + commissionMaxRateChangeIntervals = 10 +) + +type commission struct { + logger *logging.Logger + + rules staking.CommissionScheduleRules + account signature.Signer + reckonedNonce uint64 + fundingAccount signature.Signer +} + +// currentBound returns the rate bounds at the latest bound step that has +// started or nil if no step has started. +func currentBound(cs *staking.CommissionSchedule, now epochtime.EpochTime) *staking.CommissionRateBoundStep { + var latestStartedStep *staking.CommissionRateBoundStep + for i := range cs.Bounds { + step := &cs.Bounds[i] + if step.Start > now { + break + } + latestStartedStep = step + } + return latestStartedStep +} + +// genValidRateStep generates a commission rate step that conforms to all bound +// rules between start and end epoch. The function panics in case a rate step +// cannot satisfy all bound rules so the caller should make sure that bounds +// are not exclusive. +func genValidRateStep(rng *rand.Rand, logger *logging.Logger, schedule staking.CommissionSchedule, startEpoch epochtime.EpochTime, endEpoch epochtime.EpochTime) staking.CommissionRateStep { + startBound := currentBound(&schedule, startEpoch) + minBound := startBound.RateMin.ToBigInt().Int64() + maxBound := startBound.RateMax.ToBigInt().Int64() + for _, bound := range schedule.Bounds { + // Skip bounds before start bound. + if bound.Start <= startBound.Start { + continue + } + // Stop in case a bound after end is reached. + if bound.Start >= endEpoch { + break + } + boundMin := bound.RateMin.ToBigInt().Int64() + boundMax := bound.RateMax.ToBigInt().Int64() + if minBound < boundMin { + minBound = boundMin + } + if maxBound > boundMax { + maxBound = boundMax + } + } + + if minBound > maxBound { + logger.Error("genValidRateStep: cannot satisfy all bound rules", + "min_bound", minBound, + "max_bound", maxBound, + "start_epoch", startEpoch, + "end_epoch", endEpoch, + "schedule", schedule, + ) + panic("genValidRateStep: cannot satisfy all bound rules!") + } + + // [minBound, maxBound] + rate := rng.Int63n(maxBound-minBound+1) + minBound + step := staking.CommissionRateStep{Start: startEpoch} + _ = step.Rate.FromInt64(rate) + + return step +} + +// findNextExclusiveBound finds the next Bound step that has bounds which are +// exclusive with current bound step. Returns nil in case there is no exclusive +// bound step. +func findNextExclusiveBound(bounds []staking.CommissionRateBoundStep, currentBound *staking.CommissionRateBoundStep) *staking.CommissionRateBoundStep { + currentMin := currentBound.RateMin + currentMax := currentBound.RateMax + for _, bound := range bounds { + if bound.Start <= currentBound.Start { + continue + } + // newMin > currentMax || newMax < currentMin + if bound.RateMin.Cmp(¤tMax) == 1 || bound.RateMax.Cmp(¤tMin) == -1 { + return &bound + } + // Update bounds. + // newMin > currentMin + if bound.RateMin.Cmp(¤tMin) == 1 { + currentMin = bound.RateMin + } + // newMax < currentMax + if bound.RateMax.Cmp(¤tMax) == -1 { + currentMax = bound.RateMax + } + } + return nil +} + +func (c *commission) doAmendCommissionSchedule(ctx context.Context, rng *rand.Rand, cnsc consensus.ClientBackend, stakingClient staking.Backend) error { + c.logger.Debug("amend commission schedule") + + // Get current epoch. + currentEpoch, err := cnsc.GetEpoch(ctx, consensus.HeightLatest) + if err != nil { + return fmt.Errorf("GetEpoch: %w", err) + } + + var account *staking.Account + account, err = stakingClient.AccountInfo(ctx, &staking.OwnerQuery{ + Height: consensus.HeightLatest, + Owner: c.account.Public(), + }) + if err != nil { + return fmt.Errorf("stakingClient.AccountInfo %s: %w", c.account.Public(), err) + } + existingCommissionSchedule := account.Escrow.CommissionSchedule + existingCommissionSchedule.Prune(currentEpoch) + + // First epoch at which bound steps can be altered is + // `currentEpoch + RateBoundLead + 1` + // Note: Another +1 bellow since the epoch could have changed before this + // transaction is submitted. + nextAllowedBoundChangeEpoch := currentEpoch + c.rules.RateBoundLead + 1 + 1 + // Find first epoch after `nextAllowedBoundChangeEpoch` aligned with + // RateChangeInterval. + nextAlignedBoundChangeEpoch := (((nextAllowedBoundChangeEpoch - 1) / c.rules.RateChangeInterval) + 1) * c.rules.RateChangeInterval + + // Check existing bound steps. In case there are existing steps for epoch + // before `nextAlignedBoundChangeEpoch`, those cannot get amended and also + // won't be pruned yet. Therefore we need to count those to not go over the + // max rules allowed limit. + maxBoundSteps := c.rules.MaxBoundSteps + for _, step := range existingCommissionSchedule.Bounds { + if step.Start < nextAlignedBoundChangeEpoch { + maxBoundSteps-- + continue + } + break + } + + // Generate bound steps. + // [1, maxBoundSteps] + nBoundSteps := rng.Intn(maxBoundSteps) + 1 + var amendSchedule staking.AmendCommissionSchedule + boundEpoch := nextAlignedBoundChangeEpoch + for i := 0; i < nBoundSteps; i++ { + // [10, 100_000] + maxBound := rng.Int63n(100_000-10+1) + 10 + // [0, maxBound] + minBound := rng.Int63n(maxBound + 1) + + bound := staking.CommissionRateBoundStep{ + Start: boundEpoch, + } + if err = bound.RateMin.FromInt64(minBound); err != nil { + return fmt.Errorf("Rate.FromInt64 err: %w", err) + } + if err = bound.RateMax.FromInt64(maxBound); err != nil { + return fmt.Errorf("Rate.FromInt64 err: %w", err) + } + amendSchedule.Amendment.Bounds = append(amendSchedule.Amendment.Bounds, bound) + + // Set epoch for next bound. + boundEpoch = boundEpoch + (epochtime.EpochTime(rng.Intn(commissionMaxBoundChangeIntervals)+1) * c.rules.RateChangeInterval) + } + + // newSchedule is a schedule that contains all bounds that will be in effect + // once the amendment will be submitted. It contains existing bounds that + // are not yet pruned and won't be amended, and new bounds that will be + // added. + var newSchedule staking.CommissionSchedule + // Keep existing steps that wont be amended. + for _, bound := range existingCommissionSchedule.Bounds { + if bound.Start >= amendSchedule.Amendment.Bounds[0].Start { + break + } + newSchedule.Bounds = append(newSchedule.Bounds, bound) + } + // Add new steps. + newSchedule.Bounds = append(newSchedule.Bounds, amendSchedule.Amendment.Bounds...) + + // Generate rate steps. + // First epoch on which rule steps can be altered is the next epoch. + // Note: Another +1 bellow since the epoch could have changed before this + // transaction is submitted. + nextAllowedRateChangeEpoch := currentEpoch + 1 + 1 + // Find first epoch after nextAllowedRateChangeEpoch aligned with + // RateChangeInterval. + nextAlignedRateChangeEpoch := (((nextAllowedRateChangeEpoch - 1) / c.rules.RateChangeInterval) + 1) * c.rules.RateChangeInterval + // Rate start epoch. + startEpoch := nextAlignedRateChangeEpoch + // In the case when there are no existing bound steps (or none yet active), + // the startEpoch needs to match the first bound rule epoch. + if len(existingCommissionSchedule.Bounds) == 0 || existingCommissionSchedule.Bounds[0].Start > (currentEpoch+1) { + startEpoch = newSchedule.Bounds[0].Start + } else if startEpoch > amendSchedule.Amendment.Bounds[0].Start { + // Else if there are already active rules, make sure that the initial + // rule epoch is not greater than the initial bound epoch. Otherwise the + // initial bound rule could invalidate an existing rate rule. + startEpoch = amendSchedule.Amendment.Bounds[0].Start + } + // Check existing rate steps. In case there are existing steps for epoch + // before `startEpoch`, those cannot get amended and also won't be pruned + // yet. Therefore we need to count those to not go over the max rules + // allowed limit. + maxRateSteps := c.rules.MaxRateSteps + for _, step := range existingCommissionSchedule.Rates { + if step.Start < startEpoch { + maxRateSteps-- + continue + } + break + } + // [1, maxRateSteps] + nMinRateSteps := rng.Intn(maxRateSteps) + 1 + + // In some cases we might need more rate steps to satisfy all bound steps. + var needMoreRateStpes bool + for i := 0; i < nMinRateSteps || needMoreRateStpes; i++ { + // startEpoch + rng[1, commissionMaxRateChangeIntervals]*RateChangeInterval + endEpoch := startEpoch + (epochtime.EpochTime(rng.Intn(commissionMaxRateChangeIntervals)+1) * c.rules.RateChangeInterval) + + // Get active bound at start epoch. + currentBound := currentBound(&newSchedule, startEpoch) + if currentBound == nil { + // This is not expected to ever happen. + c.logger.Error("no active bound at epoch", + "epoch", startEpoch, + "schedule", newSchedule, + ) + return fmt.Errorf("txsource/commission: no active bound") + } + // Find first following exclusive bound. + nextBound := findNextExclusiveBound(newSchedule.Bounds, currentBound) + c.logger.Debug("finding next exclusive bound", + "current_bound", currentBound, + "epoch", startEpoch, + "end_epoch", endEpoch, + "bounds", newSchedule.Bounds, + "next_bound", nextBound, + "need_more", needMoreRateStpes, + ) + switch nextBound { + case nil: + // No exclusive bounds, endEpoch can remain as it is. + // No more rate steps needed. + needMoreRateStpes = false + // If we are in last step and no exclusive bounds remain, generate + // a rate that will satisfy all remaining bounds. + if i >= nMinRateSteps-1 { + endEpoch = newSchedule.Bounds[len(newSchedule.Bounds)-1].Start + 1 + } + default: + // There is an exclusive bound at nextBound.Start. + // This rule can be valid for at most until nextBound.Start. + if endEpoch > nextBound.Start { + endEpoch = nextBound.Start + } + // More steps are needed to satisfy remaining bounds. + needMoreRateStpes = true + } + + c.logger.Debug("Generating valid rate step", + "start_epoch", startEpoch, + "end_epoch", endEpoch, + "bounds", newSchedule.Bounds, + "need_more", needMoreRateStpes, + ) + step := genValidRateStep(rng, c.logger, newSchedule, startEpoch, endEpoch) + amendSchedule.Amendment.Rates = append(amendSchedule.Amendment.Rates, step) + + // Next rate should start at endEpoch. + startEpoch = endEpoch + } + + // In some cases the above procedure can produce invalid amendment. + // This happens when more than number of allowed amendment rate steps are + // needed to satisfy all bound steps. + if len(amendSchedule.Amendment.Rates) > maxRateSteps { + c.logger.Debug("To many rate steps needed to satisfy bonds, skipping amendment", + "amendment", amendSchedule, + ) + return nil + } + + // Generate transaction. + tx := staking.NewAmendCommissionScheduleTx(c.reckonedNonce, &transaction.Fee{}, &amendSchedule) + c.reckonedNonce++ + + // Estimate gas. + gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ + Caller: c.account.Public(), + Transaction: tx, + }) + if err != nil { + return fmt.Errorf("failed to estimate gas: %w", err) + } + tx.Fee.Gas = gas + feeAmount := int64(gas) * gasPrice + if err = tx.Fee.Amount.FromInt64(feeAmount); err != nil { + return fmt.Errorf("fee amount from int64: %w", err) + } + + // Fund account to cover AmendCommissionSchedule transaction fees. + fundAmount := int64(gas) * gasPrice // transaction costs + if err = transferFunds(ctx, c.logger, cnsc, c.fundingAccount, c.account.Public(), fundAmount); err != nil { + return fmt.Errorf("account funding failure: %w", err) + } + + // Sign transaction. + signedTx, err := transaction.Sign(c.account, tx) + if err != nil { + return fmt.Errorf("transaction.Sign: %w", err) + } + + c.logger.Debug("submitting amend commission schedule transaction", + "account", c.account.Public(), + "amendment", amendSchedule, + "existing", existingCommissionSchedule, + ) + + // Submit transaction. + if err = cnsc.SubmitTx(ctx, signedTx); err != nil { + return fmt.Errorf("cnsc.SubmitTx: %w", err) + } + + return nil +} + +func (c *commission) Run(gracefulExit context.Context, rng *rand.Rand, conn *grpc.ClientConn, cnsc consensus.ClientBackend, fundingAccount signature.Signer) error { + var err error + ctx := context.Background() + + c.logger = logging.GetLogger("cmd/txsource/workload/commission") + c.fundingAccount = fundingAccount + + fac := memorySigner.NewFactory() + c.account, err = fac.Generate(signature.SignerEntity, rng) + if err != nil { + return fmt.Errorf("memory signer factory Generate account: %w", err) + } + + stakingClient := staking.NewStakingClient(conn) + + params, err := stakingClient.ConsensusParameters(ctx, consensus.HeightLatest) + if err != nil { + return fmt.Errorf("stakingClient.ConsensusParameters failure: %w", err) + } + c.rules = params.CommissionScheduleRules + + for { + if err = c.doAmendCommissionSchedule(ctx, rng, cnsc, stakingClient); err != nil { + return err + } + + select { + case <-time.After(1 * time.Second): + case <-gracefulExit.Done(): + c.logger.Debug("time's up") + return nil + } + } +} diff --git a/go/oasis-node/cmd/debug/txsource/workload/workload.go b/go/oasis-node/cmd/debug/txsource/workload/workload.go index d7598efb5e5..7f69af5dd4c 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/workload.go +++ b/go/oasis-node/cmd/debug/txsource/workload/workload.go @@ -174,6 +174,7 @@ type Workload interface { // ByName is the registry of workloads that you can access with `--workload ` on the command line. var ByName = map[string]Workload{ + NameCommission: &commission{}, NameDelegation: &delegation{}, NameOversized: oversized{}, NameParallel: parallel{}, diff --git a/go/oasis-test-runner/scenario/e2e/txsource.go b/go/oasis-test-runner/scenario/e2e/txsource.go index a3333ed1a6b..4123cc04fe0 100644 --- a/go/oasis-test-runner/scenario/e2e/txsource.go +++ b/go/oasis-test-runner/scenario/e2e/txsource.go @@ -36,6 +36,7 @@ const ( var TxSourceMultiShort scenario.Scenario = &txSourceImpl{ basicImpl: *newBasicImpl("txsource-multi-short", "", nil), workloads: []string{ + workload.NameCommission, workload.NameDelegation, workload.NameOversized, workload.NameParallel, @@ -52,6 +53,7 @@ var TxSourceMultiShort scenario.Scenario = &txSourceImpl{ var TxSourceMulti scenario.Scenario = &txSourceImpl{ basicImpl: *newBasicImpl("txsource-multi", "", nil), workloads: []string{ + workload.NameCommission, workload.NameDelegation, workload.NameOversized, workload.NameParallel, diff --git a/go/staking/api/commission.go b/go/staking/api/commission.go index 58fad617635..5155e23c2ff 100644 --- a/go/staking/api/commission.go +++ b/go/staking/api/commission.go @@ -98,8 +98,8 @@ func (cs *CommissionSchedule) validateAmendmentAcceptable(rules *CommissionSched return nil } -// prune discards past steps that aren't in effect anymore. -func (cs *CommissionSchedule) prune(now epochtime.EpochTime) { +// Prune discards past steps that aren't in effect anymore. +func (cs *CommissionSchedule) Prune(now epochtime.EpochTime) { for len(cs.Rates) > 1 { if cs.Rates[1].Start > now { // Remaining steps haven't started yet, so keep them and the current active one. @@ -245,7 +245,7 @@ func (cs *CommissionSchedule) PruneAndValidateForGenesis(rules *CommissionSchedu } // If we, for example, import a snapshot as a genesis document, the current steps might not be cued up. So run a // prune step too at this time. - cs.prune(now) + cs.Prune(now) if err := cs.validateWithinBound(now); err != nil { return errors.Wrap(err, "after pruning") } @@ -264,7 +264,7 @@ func (cs *CommissionSchedule) AmendAndPruneAndValidate(amendment *CommissionSche if err := amendment.validateAmendmentAcceptable(rules, now); err != nil { return errors.Wrap(err, "amendment") } - cs.prune(now) + cs.Prune(now) cs.amend(amendment) if err := cs.validateComplexity(rules); err != nil { return errors.Wrap(err, "after pruning and amending") diff --git a/tests/fixture-data/txsource/staking-genesis.json b/tests/fixture-data/txsource/staking-genesis.json index bd983d2fea9..0b93ccd147b 100644 --- a/tests/fixture-data/txsource/staking-genesis.json +++ b/tests/fixture-data/txsource/staking-genesis.json @@ -1,14 +1,20 @@ { "params": { + "commission_schedule_rules": { + "max_bound_steps": 12, + "max_rate_steps": 12, + "rate_bound_lead": 30, + "rate_change_interval": 10 + }, "debonding_interval": 2, + "fee_split_propose": "1", + "fee_split_vote": "1", "gas_costs": { - "transfer": 10, - "burn": 10, "add_escrow": 10, - "reclaim_escrow": 10 - }, - "fee_split_vote": "1", - "fee_split_propose": "1" + "burn": 10, + "reclaim_escrow": 10, + "transfer": 10 + } }, "total_supply": "90000000000", "ledger": {