From e32c6dc73830ec8104536b54a526b3b6d658fe3a Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Tue, 23 Apr 2024 04:09:05 +0200 Subject: [PATCH 1/4] go/registry/api: Refactor SanityCheckStake --- .../apps/supplementarysanity/checks.go | 10 +- go/genesis/api/sanity_check.go | 14 +- go/registry/api/api.go | 6 +- go/registry/api/sanity_check.go | 188 +++++------------- go/staking/api/sanity_check.go | 109 ++++++++++ 5 files changed, 176 insertions(+), 151 deletions(-) diff --git a/go/consensus/cometbft/apps/supplementarysanity/checks.go b/go/consensus/cometbft/apps/supplementarysanity/checks.go index 74d7112b034..93fb6f7a265 100644 --- a/go/consensus/cometbft/apps/supplementarysanity/checks.go +++ b/go/consensus/cometbft/apps/supplementarysanity/checks.go @@ -8,11 +8,13 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/quantity" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" governanceState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/governance/state" + churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state" secretsState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/secrets/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" roothashState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/state" stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" governance "github.com/oasisprotocol/oasis-core/go/governance/api" + "github.com/oasisprotocol/oasis-core/go/keymanager/churp" "github.com/oasisprotocol/oasis-core/go/keymanager/secrets" registry "github.com/oasisprotocol/oasis-core/go/registry/api" roothash "github.com/oasisprotocol/oasis-core/go/roothash/api" @@ -417,5 +419,11 @@ func checkStakeClaims(ctx *abciAPI.Context, _ beacon.EpochTime) error { } } - return registry.SanityCheckStake(entities, accounts, nodes, runtimes, runtimes, stakingParams.Thresholds, false) + // Generate escrows. + escrows := make(map[staking.Address]*staking.EscrowAccount) + if err = registry.AddStakeClaims(entities, nodes, runtimes, runtimes, escrows); err != nil { + return err + } + + return staking.SanityCheckStake(accounts, escrows, stakingParams.Thresholds, false) } diff --git a/go/genesis/api/sanity_check.go b/go/genesis/api/sanity_check.go index 12eaa57acad..5f6b265c52f 100644 --- a/go/genesis/api/sanity_check.go +++ b/go/genesis/api/sanity_check.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" - "github.com/oasisprotocol/oasis-core/go/common/logging" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) // SanityCheck does basic sanity checking on the contents of the genesis document. @@ -31,14 +31,14 @@ func (d *Document) SanityCheck() error { } epoch := d.Beacon.Base // Note: d.Height has no easy connection to the epoch. + escrows := make(map[staking.Address]*staking.EscrowAccount) + if err := d.Registry.SanityCheck( d.Time, uint64(d.Height), epoch, - d.Staking.Ledger, - d.Staking.Parameters.Thresholds, pkBlacklist, - logging.NewNopLogger(), + escrows, ); err != nil { return err } @@ -54,5 +54,9 @@ func (d *Document) SanityCheck() error { if err := d.Scheduler.SanityCheck(&d.Staking.TotalSupply, d.Scheduler.Parameters.VotingPowerDistribution); err != nil { return err } - return d.Governance.SanityCheck(epoch, &d.Staking.GovernanceDeposits) + if err := d.Governance.SanityCheck(epoch, &d.Staking.GovernanceDeposits); err != nil { + return err + } + + return staking.SanityCheckStake(d.Staking.Ledger, escrows, d.Staking.Parameters.Thresholds, true) } diff --git a/go/registry/api/api.go b/go/registry/api/api.go index b1f17072a46..076970f64b0 100644 --- a/go/registry/api/api.go +++ b/go/registry/api/api.go @@ -1539,13 +1539,13 @@ func StakeThresholdsForNode(n *node.Node, rts []*Runtime) (thresholds []staking. } // Add runtime-specific role thresholds for each registered runtime. - seen := make(map[common.Namespace]bool) + seen := make(map[common.Namespace]struct{}) for _, nodeRt := range n.Runtimes { // A runtime can be included multiple times due to multiple deployments/versions. - if seen[nodeRt.ID] { + if _, ok := seen[nodeRt.ID]; ok { continue } - seen[nodeRt.ID] = true + seen[nodeRt.ID] = struct{}{} // Grab the runtime descriptor. rt, exists := runtimes[nodeRt.ID] diff --git a/go/registry/api/sanity_check.go b/go/registry/api/sanity_check.go index bac0323fb41..ab31f1ce352 100644 --- a/go/registry/api/sanity_check.go +++ b/go/registry/api/sanity_check.go @@ -11,7 +11,6 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/entity" "github.com/oasisprotocol/oasis-core/go/common/logging" "github.com/oasisprotocol/oasis-core/go/common/node" - "github.com/oasisprotocol/oasis-core/go/common/quantity" "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) @@ -47,11 +46,11 @@ func (g *Genesis) SanityCheck( now time.Time, height uint64, baseEpoch beacon.EpochTime, - stakeLedger map[staking.Address]*staking.Account, - stakeThresholds map[staking.ThresholdKind]quantity.Quantity, publicKeyBlacklist map[signature.PublicKey]bool, - logger *logging.Logger, + escrows map[staking.Address]*staking.EscrowAccount, ) error { + logger := logging.NewNopLogger() + if err := g.Parameters.SanityCheck(); err != nil { return fmt.Errorf("registry: sanity check failed: %w", err) } @@ -97,21 +96,23 @@ func (g *Genesis) SanityCheck( } } - if !g.Parameters.DebugBypassStake { - nodes, err := nodeLookup.Nodes(context.Background()) - if err != nil { - return fmt.Errorf("registry: sanity check failed: could not obtain node list from nodeLookup: %w", err) - } - // Skip suspended runtimes for computing stake claims. - runtimes, err := runtimesLookup.Runtimes(context.Background()) - if err != nil { - return fmt.Errorf("registry: sanity check failed: could not obtain runtimes from runtimesLookup: %w", err) - } - // Check stake. - return SanityCheckStake(entities, stakeLedger, nodes, runtimes, allRuntimes, stakeThresholds, true) + // Add stake claims. + if g.Parameters.DebugBypassStake { + return nil } - return nil + nodes, err := nodeLookup.Nodes(context.Background()) + if err != nil { + return fmt.Errorf("registry: sanity check failed: could not obtain node list from nodeLookup: %w", err) + } + + // Skip suspended runtimes for computing stake claims. + runtimes, err := runtimesLookup.Runtimes(context.Background()) + if err != nil { + return fmt.Errorf("registry: sanity check failed: could not obtain runtimes from runtimesLookup: %w", err) + } + + return AddStakeClaims(entities, nodes, runtimes, allRuntimes, escrows) } // SanityCheckEntities examines the entities table. @@ -236,85 +237,56 @@ func SanityCheckNodes( return nodeLookup, nil } -// SanityCheckStake ensures entities' stake accumulator claims are consistent -// with general state and entities have enough stake for themselves and all -// their registered nodes and runtimes. -func SanityCheckStake( +// AddStakeClaims adds stake claims for entities and all their registered nodes +// and runtimes. +func AddStakeClaims( entities []*entity.Entity, - accounts map[staking.Address]*staking.Account, nodes []*node.Node, runtimes []*Runtime, allRuntimes []*Runtime, - stakeThresholds map[staking.ThresholdKind]quantity.Quantity, - isGenesis bool, + escrows map[staking.Address]*staking.EscrowAccount, ) error { - // Entities' escrow accounts for checking claims and stake. - generatedEscrows := make(map[staking.Address]*staking.EscrowAccount) - - // Generate escrow account for all entities. for _, entity := range entities { - var escrow *staking.EscrowAccount + // Add entity stake claim. addr := staking.NewAddress(entity.ID) - acct, ok := accounts[addr] - if ok { - // Generate an escrow account with the same active balance and shares number. - escrow = &staking.EscrowAccount{ - Active: staking.SharePool{ - Balance: acct.Escrow.Active.Balance, - TotalShares: acct.Escrow.Active.TotalShares, - }, - } - } else { - // No account is associated with this entity, generate an empty escrow account. + escrow, ok := escrows[addr] + if !ok { escrow = &staking.EscrowAccount{} + escrows[addr] = escrow } - // Add entity stake claim. escrow.StakeAccumulator.AddClaimUnchecked(StakeClaimRegisterEntity, staking.GlobalStakeThresholds(staking.KindEntity)) - - generatedEscrows[addr] = escrow } - // Also generate escrow accounts for all runtimes that are using the - // runtime governance model. runtimeMap := make(map[common.Namespace]*Runtime) for _, rt := range allRuntimes { runtimeMap[rt.ID] = rt - - if rt.GovernanceModel == GovernanceRuntime { - // Generate escrow account for the runtime. - var escrow *staking.EscrowAccount - addr := staking.NewRuntimeAddress(rt.ID) - acct, ok := accounts[addr] - if ok { - escrow = &staking.EscrowAccount{ - Active: staking.SharePool{ - Balance: acct.Escrow.Active.Balance, - TotalShares: acct.Escrow.Active.TotalShares, - }, - } - } else { - escrow = &staking.EscrowAccount{} - } - - generatedEscrows[addr] = escrow - } } + var nodeRts []*Runtime for _, node := range nodes { - var nodeRts []*Runtime - rtMap := make(map[common.Namespace]bool) + rtMap := make(map[common.Namespace]struct{}) for _, rt := range node.Runtimes { - if rtMap[rt.ID] { + if _, ok := rtMap[rt.ID]; ok { continue } - rtMap[rt.ID] = true + rtMap[rt.ID] = struct{}{} nodeRts = append(nodeRts, runtimeMap[rt.ID]) } + // Add node stake claims. addr := staking.NewAddress(node.EntityID) - generatedEscrows[addr].StakeAccumulator.AddClaimUnchecked(StakeClaimForNode(node.ID), StakeThresholdsForNode(node, nodeRts)) + escrow, ok := escrows[addr] + if !ok { + escrow = &staking.EscrowAccount{} + escrows[addr] = escrow + } + + escrow.StakeAccumulator.AddClaimUnchecked(StakeClaimForNode(node.ID), StakeThresholdsForNode(node, nodeRts)) + + // Reuse slice. + nodeRts = nodeRts[:0] } for _, rt := range runtimes { // Add runtime stake claims. @@ -322,81 +294,13 @@ func SanityCheckStake( if addr == nil { continue } - - generatedEscrows[*addr].StakeAccumulator.AddClaimUnchecked(StakeClaimForRuntime(rt.ID), StakeThresholdsForRuntime(rt)) - } - - // Compare entities' generated escrow accounts with actual ones. - for _, entity := range entities { - var generatedEscrow, actualEscrow *staking.EscrowAccount - addr := staking.NewAddress(entity.ID) - generatedEscrow = generatedEscrows[addr] - acct, ok := accounts[addr] - if ok { - actualEscrow = &acct.Escrow - } else { - // No account is associated with this entity, generate an empty escrow account. - actualEscrow = &staking.EscrowAccount{} + escrow, ok := escrows[*addr] + if !ok { + escrow = &staking.EscrowAccount{} + escrows[*addr] = escrow } - if isGenesis { - // For a Genesis document, check if the entity has enough stake for all its stake claims. - // NOTE: We can't perform this check at an arbitrary point since the entity could - // reclaim its stake from the escrow but its nodes and/or runtimes will only be - // ineligible/suspended at the next epoch transition. - if err := generatedEscrow.CheckStakeClaims(stakeThresholds); err != nil { - expected := "unknown" - expectedQty, err2 := generatedEscrow.StakeAccumulator.TotalClaims(stakeThresholds, nil) - if err2 == nil { - expected = expectedQty.String() - } - return fmt.Errorf("insufficient stake for account %s (expected: %s got: %s): %w", - addr, - expected, - generatedEscrow.Active.Balance, - err, - ) - } - } else { - // Otherwise, compare the expected accumulator state with the actual one. - // NOTE: We can't perform this check for the Genesis document since it is not allowed to - // have non-empty stake accumulators. - expectedClaims := generatedEscrows[addr].StakeAccumulator.Claims - actualClaims := actualEscrow.StakeAccumulator.Claims - if len(expectedClaims) != len(actualClaims) { - return fmt.Errorf("incorrect number of stake claims for account %s (expected: %d got: %d)", - addr, - len(expectedClaims), - len(actualClaims), - ) - } - for claim, expectedThresholds := range expectedClaims { - thresholds, ok := actualClaims[claim] - if !ok { - return fmt.Errorf("missing claim %s for account %s", claim, addr) - } - if len(thresholds) != len(expectedThresholds) { - return fmt.Errorf("incorrect number of thresholds for claim %s for account %s (expected: %d got: %d)", - claim, - addr, - len(expectedThresholds), - len(thresholds), - ) - } - for i, expectedThreshold := range expectedThresholds { - threshold := thresholds[i] - if !threshold.Equal(&expectedThreshold) { // nolint: gosec - return fmt.Errorf("incorrect threshold in position %d for claim %s for account %s (expected: %s got: %s)", - i, - claim, - addr, - expectedThreshold, - threshold, - ) - } - } - } - } + escrow.StakeAccumulator.AddClaimUnchecked(StakeClaimForRuntime(rt.ID), StakeThresholdsForRuntime(rt)) } return nil diff --git a/go/staking/api/sanity_check.go b/go/staking/api/sanity_check.go index 7506142c458..2e7b71937c1 100644 --- a/go/staking/api/sanity_check.go +++ b/go/staking/api/sanity_check.go @@ -395,3 +395,112 @@ func (g *Genesis) SanityCheck(now beacon.EpochTime) error { // nolint: gocyclo return nil } + +// SanityCheckStake compares generated escrow accounts with actual ones. +func SanityCheckStake( + accounts map[Address]*Account, + escrows map[Address]*EscrowAccount, + thresholds map[ThresholdKind]quantity.Quantity, + isGenesis bool, +) error { + // For a Genesis document, check if accounts have enough stake for all its stake claims. + // NOTE: We can't perform this check at an arbitrary point since the entity could + // reclaim its stake from the escrow but its nodes and/or runtimes will only be + // ineligible/suspended at the next epoch transition. + if isGenesis { + // Populate escrow accounts with the same active balance and shares number. + for addr, escrow := range escrows { + acct, ok := accounts[addr] + if !ok { + continue + } + + escrow.Active.Balance = acct.Escrow.Active.Balance + escrow.Active.TotalShares = acct.Escrow.Active.TotalShares + } + + for addr, escrow := range escrows { + if err := escrow.CheckStakeClaims(thresholds); err != nil { + expected := "unknown" + expectedQty, err2 := escrow.StakeAccumulator.TotalClaims(thresholds, nil) + if err2 == nil { + expected = expectedQty.String() + } + return fmt.Errorf("insufficient stake for account %s (expected: %s got: %s): %w", + addr, + expected, + escrow.Active.Balance, + err, + ) + } + } + + return nil + } + + // Otherwise, compare the expected accumulator state with the actual one. + // NOTE: We can't perform this check for the Genesis document since it is not allowed to + // have non-empty stake accumulators. + seen := make(map[Address]struct{}) + for addr, escrow := range escrows { + seen[addr] = struct{}{} + + var actualEscrow EscrowAccount + acct, ok := accounts[addr] + if ok { + actualEscrow = acct.Escrow + } + + expectedClaims := escrow.StakeAccumulator.Claims + actualClaims := actualEscrow.StakeAccumulator.Claims + if len(expectedClaims) != len(actualClaims) { + return fmt.Errorf("incorrect number of stake claims for account %s (expected: %d got: %d)", + addr, + len(expectedClaims), + len(actualClaims), + ) + } + for claim, expectedThresholds := range expectedClaims { + thresholds, ok := actualClaims[claim] + if !ok { + return fmt.Errorf("missing claim %s for account %s", claim, addr) + } + if len(thresholds) != len(expectedThresholds) { + return fmt.Errorf("incorrect number of thresholds for claim %s for account %s (expected: %d got: %d)", + claim, + addr, + len(expectedThresholds), + len(thresholds), + ) + } + for i, expectedThreshold := range expectedThresholds { + threshold := thresholds[i] + if !threshold.Equal(&expectedThreshold) { // nolint: gosec + return fmt.Errorf("incorrect threshold in position %d for claim %s for account %s (expected: %s got: %s)", + i, + claim, + addr, + expectedThreshold, + threshold, + ) + } + } + } + } + + for addr, acct := range accounts { + if _, ok := seen[addr]; ok { + continue + } + + actualClaims := acct.Escrow.StakeAccumulator.Claims + if len(actualClaims) != 0 { + return fmt.Errorf("incorrect number of stake claims for account %s (expected: 0 got: %d)", + addr, + len(actualClaims), + ) + } + } + + return nil +} From c9f52fa8c4535008052f6d9218424b0d07d2450a Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Fri, 9 Feb 2024 13:41:44 +0100 Subject: [PATCH 2/4] go/keymanager/churp: Require stake for every churp scheme --- .changelog/5653.feature.md | 1 + .../cometbft/apps/keymanager/churp/txs.go | 63 ++++++- .../apps/keymanager/churp/txs_test.go | 176 ++++++++++-------- .../apps/registry/transactions_test.go | 3 + .../cometbft/apps/staking/messages_test.go | 1 + .../apps/supplementarysanity/checks.go | 9 + go/keymanager/churp/api.go | 20 ++ go/keymanager/churp/sanity_check.go | 47 +++++ go/oasis-node/cmd/common/genesis/staking.go | 1 + .../cmd/debug/txsource/workload/queries.go | 6 +- go/oasis-node/cmd/stake/stake.go | 1 + .../scenario/e2e/runtime/keymanager_churp.go | 13 +- .../scenario/e2e/runtime/runtime_dynamic.go | 1 + .../e2e/runtime/storage_early_state_sync.go | 1 + .../scenario/e2e/runtime/txsource.go | 1 + go/oasis-test-runner/scenario/e2e/upgrade.go | 28 +++ go/staking/api/api.go | 11 +- go/staking/api/api_test.go | 2 + go/staking/tests/state.go | 1 + go/staking/tests/tester.go | 1 + go/upgrade/migrations/consensus_240.go | 20 +- runtime/src/transaction/dispatcher.rs | 4 +- 22 files changed, 318 insertions(+), 93 deletions(-) create mode 100644 .changelog/5653.feature.md create mode 100644 go/keymanager/churp/sanity_check.go diff --git a/.changelog/5653.feature.md b/.changelog/5653.feature.md new file mode 100644 index 00000000000..756636605fd --- /dev/null +++ b/.changelog/5653.feature.md @@ -0,0 +1 @@ +go/keymanager/churp: Require stake for every churp scheme diff --git a/go/consensus/cometbft/apps/keymanager/churp/txs.go b/go/consensus/cometbft/apps/keymanager/churp/txs.go index 7853c116390..a01126e11d5 100644 --- a/go/consensus/cometbft/apps/keymanager/churp/txs.go +++ b/go/consensus/cometbft/apps/keymanager/churp/txs.go @@ -5,14 +5,17 @@ import ( "sort" beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" + "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/node" tmapi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state" - "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/common" + kmCommon "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/common" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" + stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" "github.com/oasisprotocol/oasis-core/go/registry/api" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) func (ext *churpExt) create(ctx *tmapi.Context, req *churp.CreateRequest) error { @@ -34,7 +37,7 @@ func (ext *churpExt) create(ctx *tmapi.Context, req *churp.CreateRequest) error } // Ensure that the runtime exists and is a key manager. - kmRt, err := common.KeyManagerRuntime(ctx, req.RuntimeID) + kmRt, err := kmCommon.KeyManagerRuntime(ctx, req.RuntimeID) if err != nil { return err } @@ -75,13 +78,20 @@ func (ext *churpExt) create(ctx *tmapi.Context, req *churp.CreateRequest) error } } - // TODO: Add stake claim. - // Return early if this is a CheckTx context. if ctx.IsCheckOnly() { return nil } + // Start a new transaction and rollback in case we fail. + ctx = ctx.NewTransaction() + defer ctx.Close() + + // Add stake claim to the key manager owner. + if err = addStakeClaim(ctx, kmRt.EntityID, req.RuntimeID, req.ID); err != nil { + return err + } + // Create a new instance. status := churp.Status{ Identity: req.Identity, @@ -109,6 +119,8 @@ func (ext *churpExt) create(ctx *tmapi.Context, req *churp.CreateRequest) error Status: &status, })) + ctx.Commit() + return nil } @@ -131,7 +143,7 @@ func (ext *churpExt) update(ctx *tmapi.Context, req *churp.UpdateRequest) error } // Ensure that the runtime exists and is a key manager. - kmRt, err := common.KeyManagerRuntime(ctx, req.RuntimeID) + kmRt, err := kmCommon.KeyManagerRuntime(ctx, req.RuntimeID) if err != nil { return err } @@ -224,7 +236,7 @@ func (ext *churpExt) apply(ctx *tmapi.Context, req *churp.SignedApplicationReque } // Ensure that the runtime exists and is a key manager. - kmRt, err := common.KeyManagerRuntime(ctx, req.Application.RuntimeID) + kmRt, err := kmCommon.KeyManagerRuntime(ctx, req.Application.RuntimeID) if err != nil { return err } @@ -316,7 +328,7 @@ func (ext *churpExt) confirm(ctx *tmapi.Context, req *churp.SignedConfirmationRe } // Ensure that the runtime exists and is a key manager. - kmRt, err := common.KeyManagerRuntime(ctx, req.Confirmation.RuntimeID) + kmRt, err := kmCommon.KeyManagerRuntime(ctx, req.Confirmation.RuntimeID) if err != nil { return err } @@ -417,6 +429,39 @@ func (ext *churpExt) computeNextHandoff(ctx *tmapi.Context) (beacon.EpochTime, e return epoch + 1, nil } +func addStakeClaim(ctx *tmapi.Context, entityID signature.PublicKey, runtimeID common.Namespace, churpID uint8) error { + regState := registryState.NewMutableState(ctx.State()) + + regParams, err := regState.ConsensusParameters(ctx) + if err != nil { + return err + } + if regParams.DebugBypassStake { + return nil + } + + entityAddr := staking.NewAddress(entityID) + if err != nil { + return err + } + + claim := churp.StakeClaim(runtimeID, churpID) + thresholds := churp.StakeThresholds() + + if err = stakingState.AddStakeClaim(ctx, entityAddr, claim, thresholds); err != nil { + ctx.Logger().Debug("keymanager: churp: insufficient stake", + "err", err, + "entity", entityID, + "runtime", runtimeID, + "churp", churpID, + "account", entityAddr, + ) + return fmt.Errorf("keymanager: churp: insufficient stake: %w", err) + } + + return nil +} + func runtimeAttestationKey(ctx *tmapi.Context, nodeID signature.PublicKey, now beacon.EpochTime, kmRt *api.Runtime) (*signature.PublicKey, error) { regState := registryState.NewMutableState(ctx.State()) @@ -433,11 +478,11 @@ func runtimeAttestationKey(ctx *tmapi.Context, nodeID signature.PublicKey, now b } // Fetch RAK. - nodeRt, err := common.NodeRuntime(n, kmRt.ID) + nodeRt, err := kmCommon.NodeRuntime(n, kmRt.ID) if err != nil { return nil, err } - rak, err := common.RuntimeAttestationKey(nodeRt, kmRt) + rak, err := kmCommon.RuntimeAttestationKey(nodeRt, kmRt) if err != nil { return nil, fmt.Errorf("keymanager: churp: failed to fetch node's rak: %w", err) } diff --git a/go/consensus/cometbft/apps/keymanager/churp/txs_test.go b/go/consensus/cometbft/apps/keymanager/churp/txs_test.go index 37faefb80e8..372b3371da4 100644 --- a/go/consensus/cometbft/apps/keymanager/churp/txs_test.go +++ b/go/consensus/cometbft/apps/keymanager/churp/txs_test.go @@ -15,11 +15,14 @@ import ( memorySigner "github.com/oasisprotocol/oasis-core/go/common/crypto/signature/signers/memory" "github.com/oasisprotocol/oasis-core/go/common/entity" "github.com/oasisprotocol/oasis-core/go/common/node" + "github.com/oasisprotocol/oasis-core/go/common/quantity" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" + stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" - registryAPI "github.com/oasisprotocol/oasis-core/go/registry/api" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) const ( @@ -50,11 +53,13 @@ type TxTestSuite struct { txCtx *abciAPI.Context cfg *abciAPI.MockApplicationStateConfig - state *churpState.MutableState + state *churpState.MutableState + regState *registryState.MutableState + stakeState *stakingState.MutableState nodes []*testNode - computeRuntimes []*registryAPI.Runtime - keymanagerRuntimes []*registryAPI.Runtime + computeRuntimes []*registry.Runtime + keymanagerRuntimes []*registry.Runtime entity *testEntity } @@ -72,11 +77,16 @@ func (s *TxTestSuite) SetupTest() { // Prepare states. s.state = churpState.NewMutableState(s.ctx.State()) - regState := registryState.NewMutableState(s.ctx.State()) + s.regState = registryState.NewMutableState(s.ctx.State()) + s.stakeState = stakingState.NewMutableState(s.ctx.State()) // Set up default consensus parameters. err := s.state.SetConsensusParameters(s.ctx, &churp.DefaultConsensusParameters) require.NoError(s.T(), err) + err = s.regState.SetConsensusParameters(s.ctx, ®istry.ConsensusParameters{}) + require.NoError(s.T(), err) + err = s.stakeState.SetConsensusParameters(s.ctx, &staking.ConsensusParameters{}) + require.NoError(s.T(), err) // Prepare nodes. s.nodes = make([]*testNode, 0, numNodes) @@ -100,32 +110,32 @@ func (s *TxTestSuite) SetupTest() { Nodes: nodes, } - sigEnt, err := entity.SignEntity(s.entity.signer, registryAPI.RegisterEntitySignatureContext, &ent) + sigEnt, err := entity.SignEntity(s.entity.signer, registry.RegisterEntitySignatureContext, &ent) require.NoError(s.T(), err) - err = regState.SetEntity(s.ctx, &ent, sigEnt) + err = s.regState.SetEntity(s.ctx, &ent, sigEnt) require.NoError(s.T(), err) // Prepare and register runtimes. for i := 0; i < numComputeRuntimes; i++ { - s.computeRuntimes = append(s.computeRuntimes, ®istryAPI.Runtime{ + s.computeRuntimes = append(s.computeRuntimes, ®istry.Runtime{ ID: common.NewTestNamespaceFromSeed([]byte{0, byte(i)}, common.NamespaceTest), - Kind: registryAPI.KindCompute, + Kind: registry.KindCompute, EntityID: s.entity.signer.Public(), }) - err = regState.SetRuntime(s.ctx, s.computeRuntimes[i], false) + err = s.regState.SetRuntime(s.ctx, s.computeRuntimes[i], false) require.NoError(s.T(), err) } for i := 0; i < numKeyManagerRuntimes; i++ { - s.keymanagerRuntimes = append(s.keymanagerRuntimes, ®istryAPI.Runtime{ + s.keymanagerRuntimes = append(s.keymanagerRuntimes, ®istry.Runtime{ ID: common.NewTestNamespaceFromSeed([]byte{1, byte(i)}, common.NamespaceTest), - Kind: registryAPI.KindKeyManager, + Kind: registry.KindKeyManager, TEEHardware: node.TEEHardwareIntelSGX, EntityID: s.entity.signer.Public(), }) - err = regState.SetRuntime(s.ctx, s.keymanagerRuntimes[i], false) + err = s.regState.SetRuntime(s.ctx, s.keymanagerRuntimes[i], false) require.NoError(s.T(), err) } @@ -154,9 +164,9 @@ func (s *TxTestSuite) SetupTest() { }) } - sigNode, nErr := node.MultiSignNode([]signature.Signer{s.nodes[i].signer}, registryAPI.RegisterNodeSignatureContext, n) + sigNode, nErr := node.MultiSignNode([]signature.Signer{s.nodes[i].signer}, registry.RegisterNodeSignatureContext, n) require.NoError(s.T(), nErr) - err = regState.SetNode(s.ctx, nil, n, sigNode) + err = s.regState.SetNode(s.ctx, nil, n, sigNode) require.NoError(s.T(), err) } @@ -172,11 +182,8 @@ func (s *TxTestSuite) TearDownTest() { func (s *TxTestSuite) TestCreate() { s.Run("not key manager runtime", func() { - req := churp.CreateRequest{ - Identity: churp.Identity{ - RuntimeID: s.computeRuntimes[0].ID, - }, - } + req := s.createRequest(0) + req.Identity.RuntimeID = s.computeRuntimes[0].ID err := s.ext.create(s.txCtx, &req) require.ErrorContains(s.T(), err, "runtime is not a key manager") }) @@ -185,44 +192,20 @@ func (s *TxTestSuite) TestCreate() { s.txCtx.SetTxSigner(s.nodes[0].signer.Public()) defer s.txCtx.SetTxSigner(s.entity.signer.Public()) - req := churp.CreateRequest{ - Identity: churp.Identity{ - RuntimeID: s.keymanagerRuntimes[0].ID, - }, - } + req := s.createRequest(0) err := s.ext.create(s.txCtx, &req) require.ErrorContains(s.T(), err, "invalid signer") }) s.Run("invalid config", func() { - req := churp.CreateRequest{ - Identity: churp.Identity{ - RuntimeID: s.keymanagerRuntimes[0].ID, - }, - GroupID: 100, - } + req := s.createRequest(0) + req.GroupID = 100 err := s.ext.create(s.txCtx, &req) require.ErrorContains(s.T(), err, "invalid config: unsupported group, ID 100") }) s.Run("happy path - handoffs disabled", func() { - identity := churp.Identity{ - ID: 0, - RuntimeID: s.keymanagerRuntimes[0].ID, - } - policy := churp.SignedPolicySGX{ - Policy: churp.PolicySGX{ - Identity: identity, - }, - } - req := churp.CreateRequest{ - Identity: identity, - GroupID: churp.EccNistP384, - Threshold: 1, - ExtraShares: 2, - HandoffInterval: 0, - Policy: policy, - } + req := s.createRequest(0) err := s.ext.create(s.txCtx, &req) require.NoError(s.T(), err) @@ -233,13 +216,13 @@ func (s *TxTestSuite) TestCreate() { // Verify status. status, err := s.state.Status(s.txCtx, s.keymanagerRuntimes[0].ID, 0) require.NoError(s.T(), err) - require.Equal(s.T(), uint8(0), status.ID) + require.Equal(s.T(), req.ID, status.ID) require.Equal(s.T(), s.keymanagerRuntimes[0].ID, status.RuntimeID) require.Equal(s.T(), churp.EccNistP384, status.GroupID) - require.Equal(s.T(), uint8(1), status.Threshold) - require.Equal(s.T(), uint8(2), status.ExtraShares) + require.Equal(s.T(), req.Threshold, status.Threshold) + require.Equal(s.T(), req.ExtraShares, status.ExtraShares) require.Equal(s.T(), beacon.EpochTime(0), status.HandoffInterval) - require.Equal(s.T(), policy, status.Policy) + require.Equal(s.T(), req.Policy, status.Policy) require.Equal(s.T(), beacon.EpochTime(0), status.Handoff) require.Nil(s.T(), status.Checksum) require.Nil(s.T(), status.Committee) @@ -249,22 +232,8 @@ func (s *TxTestSuite) TestCreate() { }) s.Run("happy path - handoffs enabled", func() { - identity := churp.Identity{ - ID: 1, - RuntimeID: s.keymanagerRuntimes[0].ID, - } - policy := churp.SignedPolicySGX{ - Policy: churp.PolicySGX{ - Identity: identity, - }, - } - req := churp.CreateRequest{ - Identity: identity, - GroupID: churp.EccNistP384, - Threshold: 1, - HandoffInterval: 10, - Policy: policy, - } + req := s.createRequest(1) + req.HandoffInterval = 10 err := s.ext.create(s.txCtx, &req) require.NoError(s.T(), err) @@ -276,15 +245,54 @@ func (s *TxTestSuite) TestCreate() { }) s.Run("duplicate ID", func() { - req := churp.CreateRequest{ - Identity: churp.Identity{ - ID: 0, - RuntimeID: s.keymanagerRuntimes[0].ID, - }, - } + req := s.createRequest(0) err := s.ext.create(s.txCtx, &req) require.ErrorContains(s.T(), err, "invalid config: ID must be unique") }) + + // Require stake for creating a new scheme. + err := s.stakeState.SetConsensusParameters(s.ctx, &staking.ConsensusParameters{ + Thresholds: map[staking.ThresholdKind]quantity.Quantity{ + staking.KindKeyManagerChurp: *quantity.NewFromUint64(100), + }, + }) + require.NoError(s.T(), err) + + s.Run("not enough stake", func() { + req := s.createRequest(2) + err = s.ext.create(s.txCtx, &req) + require.ErrorContains(s.T(), err, "insufficient stake") + }) + + s.Run("enough stake", func() { + addr := staking.NewAddress(s.entity.signer.Public()) + err := s.stakeState.SetAccount(s.ctx, addr, &staking.Account{ + Escrow: staking.EscrowAccount{ + Active: staking.SharePool{ + Balance: *quantity.NewFromUint64(100), + TotalShares: *quantity.NewFromUint64(0), + }, + }, + }) + require.NoError(s.T(), err) + + req := s.createRequest(2) + err = s.ext.create(s.txCtx, &req) + require.NoError(s.T(), err) + + // Verify stake claims. + claim := churp.StakeClaim(req.RuntimeID, req.ID) + require.Equal(s.T(), "keymanager.churp.Scheme.800000000000000014372f88b4f86ae89a9e61eb324bd8b7086db8e256ebfb44.2", string(claim)) + + kind := staking.KindKeyManagerChurp + thresholds := churp.StakeThresholds() + require.Equal(s.T(), []staking.StakeThreshold{{Global: &kind}}, thresholds) + + acc, err := s.stakeState.Account(s.ctx, addr) + require.NoError(s.T(), err) + require.Equal(s.T(), 1, len(acc.Escrow.StakeAccumulator.Claims)) + require.ElementsMatch(s.T(), thresholds, acc.Escrow.StakeAccumulator.Claims[claim]) + }) } func (s *TxTestSuite) TestUpdate() { @@ -690,3 +698,23 @@ func (s *TxTestSuite) updateRequest() churp.UpdateRequest { }, } } + +func (s *TxTestSuite) createRequest(id uint8) churp.CreateRequest { + identity := churp.Identity{ + ID: id, + RuntimeID: s.keymanagerRuntimes[0].ID, + } + policy := churp.SignedPolicySGX{ + Policy: churp.PolicySGX{ + Identity: identity, + }, + } + return churp.CreateRequest{ + Identity: identity, + GroupID: churp.EccNistP384, + Threshold: 1, + ExtraShares: 2, + HandoffInterval: 0, + Policy: policy, + } +} diff --git a/go/consensus/cometbft/apps/registry/transactions_test.go b/go/consensus/cometbft/apps/registry/transactions_test.go index 5b5ab6ec750..60808a2eb0d 100644 --- a/go/consensus/cometbft/apps/registry/transactions_test.go +++ b/go/consensus/cometbft/apps/registry/transactions_test.go @@ -46,6 +46,7 @@ func TestRegisterNode(t *testing.T) { staking.KindNodeKeyManager: *quantity.NewFromUint64(0), staking.KindRuntimeCompute: *quantity.NewFromUint64(0), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(0), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(0), }, } @@ -120,6 +121,7 @@ func TestRegisterNode(t *testing.T) { staking.KindNodeKeyManager: *quantity.NewFromUint64(0), staking.KindRuntimeCompute: *quantity.NewFromUint64(0), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(0), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(0), }, }, false, @@ -299,6 +301,7 @@ func TestRegisterNode(t *testing.T) { staking.KindNodeKeyManager: *quantity.NewFromUint64(0), staking.KindRuntimeCompute: *quantity.NewFromUint64(0), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(0), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(0), }, }, false, diff --git a/go/consensus/cometbft/apps/staking/messages_test.go b/go/consensus/cometbft/apps/staking/messages_test.go index eb55cbb2da5..9338fe328e4 100644 --- a/go/consensus/cometbft/apps/staking/messages_test.go +++ b/go/consensus/cometbft/apps/staking/messages_test.go @@ -33,6 +33,7 @@ func TestChangeParameters(t *testing.T) { staking.KindNodeKeyManager: *quantity.NewFromUint64(1), staking.KindRuntimeCompute: *quantity.NewFromUint64(1), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(1), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(1), }, FeeSplitWeightVote: *quantity.NewFromUint64(1), } diff --git a/go/consensus/cometbft/apps/supplementarysanity/checks.go b/go/consensus/cometbft/apps/supplementarysanity/checks.go index 93fb6f7a265..c98e28f33ac 100644 --- a/go/consensus/cometbft/apps/supplementarysanity/checks.go +++ b/go/consensus/cometbft/apps/supplementarysanity/checks.go @@ -376,6 +376,7 @@ func checkHalt(*abciAPI.Context, beacon.EpochTime) error { func checkStakeClaims(ctx *abciAPI.Context, _ beacon.EpochTime) error { regSt := registryState.NewMutableState(ctx.State()) stakingSt := stakingState.NewMutableState(ctx.State()) + churpSt := churpState.NewMutableState(ctx.State()) regParams, err := regSt.ConsensusParameters(ctx) if err != nil { @@ -418,9 +419,17 @@ func checkStakeClaims(ctx *abciAPI.Context, _ beacon.EpochTime) error { return fmt.Errorf("failed to get staking account %s: %w", addr, err) } } + // Get key manager churp statuses. + churps, err := churpSt.AllStatuses(ctx) + if err != nil { + return fmt.Errorf("failed to get churp statuses: %w", err) + } // Generate escrows. escrows := make(map[staking.Address]*staking.EscrowAccount) + if err = churp.AddStakeClaims(churps, runtimes, escrows); err != nil { + return err + } if err = registry.AddStakeClaims(entities, nodes, runtimes, runtimes, escrows); err != nil { return err } diff --git a/go/keymanager/churp/api.go b/go/keymanager/churp/api.go index 717b6279749..f269e47c5ee 100644 --- a/go/keymanager/churp/api.go +++ b/go/keymanager/churp/api.go @@ -1,9 +1,12 @@ package churp import ( + "fmt" + "github.com/oasisprotocol/oasis-core/go/common" "github.com/oasisprotocol/oasis-core/go/common/errors" "github.com/oasisprotocol/oasis-core/go/consensus/api/transaction" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) const ( @@ -62,6 +65,23 @@ var DefaultConsensusParameters = ConsensusParameters{ GasCosts: DefaultGasCosts, } +const ( + // StakeClaimScheme is the stake claim template used for creating + // new CHURP schemes. + StakeClaimScheme = "keymanager.churp.Scheme.%s.%d" +) + +// StakeClaim generates a new stake claim identifier for a specific +// scheme creation. +func StakeClaim(runtimeID common.Namespace, churpID uint8) staking.StakeClaim { + return staking.StakeClaim(fmt.Sprintf(StakeClaimScheme, runtimeID, churpID)) +} + +// StakeThresholds returns the staking thresholds. +func StakeThresholds() []staking.StakeThreshold { + return staking.GlobalStakeThresholds(staking.KindKeyManagerChurp) +} + // NewCreateTx creates a new create transaction. func NewCreateTx(nonce uint64, fee *transaction.Fee, req *CreateRequest) *transaction.Transaction { return transaction.NewTransaction(nonce, fee, MethodCreate, req) diff --git a/go/keymanager/churp/sanity_check.go b/go/keymanager/churp/sanity_check.go new file mode 100644 index 00000000000..c9c3d58a975 --- /dev/null +++ b/go/keymanager/churp/sanity_check.go @@ -0,0 +1,47 @@ +package churp + +import ( + "fmt" + + "github.com/oasisprotocol/oasis-core/go/common" + registry "github.com/oasisprotocol/oasis-core/go/registry/api" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" +) + +// AddStakeClaims adds stake claims for the given schemes. +func AddStakeClaims(statuses []*Status, runtimes []*registry.Runtime, escrows map[staking.Address]*staking.EscrowAccount) error { + churps := make(map[common.Namespace][]uint8) + for _, status := range statuses { + churps[status.RuntimeID] = append(churps[status.RuntimeID], status.ID) + } + + thresholds := StakeThresholds() + + for _, rt := range runtimes { + ids, ok := churps[rt.ID] + if !ok { + continue + } + + addr := rt.StakingAddress() + if addr == nil { + continue + } + escrow, ok := escrows[*addr] + if !ok { + escrow = &staking.EscrowAccount{} + escrows[*addr] = escrow + } + + for _, id := range ids { + escrow.StakeAccumulator.AddClaimUnchecked(StakeClaim(rt.ID, id), thresholds) + } + delete(churps, rt.ID) + } + + if len(churps) > 0 { + return fmt.Errorf("failed to add all stake claims") + } + + return nil +} diff --git a/go/oasis-node/cmd/common/genesis/staking.go b/go/oasis-node/cmd/common/genesis/staking.go index d1d624651b2..d2df310b52b 100644 --- a/go/oasis-node/cmd/common/genesis/staking.go +++ b/go/oasis-node/cmd/common/genesis/staking.go @@ -89,6 +89,7 @@ func (st *AppendableStakingState) AppendTo(doc *genesis.Document) error { staking.KindNodeKeyManager: sq, staking.KindRuntimeCompute: sq, staking.KindRuntimeKeyManager: sq, + staking.KindKeyManagerChurp: sq, } } diff --git a/go/oasis-node/cmd/debug/txsource/workload/queries.go b/go/oasis-node/cmd/debug/txsource/workload/queries.go index f1b8c02ff3e..07264058e44 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/queries.go +++ b/go/oasis-node/cmd/debug/txsource/workload/queries.go @@ -355,7 +355,7 @@ func (q *queries) doSchedulerQueries(ctx context.Context, _ *rand.Rand, height i "epoch", epoch, "runtime_id", q.runtimeID, ) - return fmt.Errorf("missing commiteess") + return fmt.Errorf("missing committees") } } @@ -505,12 +505,12 @@ func (q *queries) doStakingQueries(ctx context.Context, rng *rand.Rand, height i } expected := q.stakingParams.Thresholds[thKind] if threshold.Cmp(&expected) != 0 { - q.logger.Error("invalid treshold", + q.logger.Error("invalid threshold", "expected", expected, "threshold", threshold, "height", height, ) - return fmt.Errorf("invalid treshold") + return fmt.Errorf("invalid threshold") } addresses, err := q.staking.Addresses(ctx, height) diff --git a/go/oasis-node/cmd/stake/stake.go b/go/oasis-node/cmd/stake/stake.go index 3279e5aed32..b4f44a3eb1c 100644 --- a/go/oasis-node/cmd/stake/stake.go +++ b/go/oasis-node/cmd/stake/stake.go @@ -245,6 +245,7 @@ func doInfo(cmd *cobra.Command, _ []string) { api.KindNodeKeyManager, api.KindRuntimeCompute, api.KindRuntimeKeyManager, + api.KindKeyManagerChurp, } for _, kind := range thresholdsToQuery { thres, err := client.Threshold(ctx, &api.ThresholdQuery{Kind: kind, Height: height}) diff --git a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp.go b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp.go index f8445e32fbf..1bceee2ce8d 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/keymanager_churp.go @@ -226,9 +226,18 @@ func (sc *kmChurpImpl) Run(ctx context.Context, _ *env.Env) error { //nolint: go nonce++ // After the update, there should be no status updates. - <-stCh + for status.HandoffInterval != churp.HandoffsDisabled { + status, err = sc.nextChurpStatus(ctx, stCh) + if err != nil { + return err + } + } + select { - case <-stCh: + case status = <-stCh: + sc.Logger.Info("status updated", + "status", fmt.Sprintf("%+v", status), + ) return fmt.Errorf("handoffs should be disabled") case <-ctx.Done(): return ctx.Err() diff --git a/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go b/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go index a0f7b1e1f0b..5270ff519d2 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/runtime_dynamic.go @@ -57,6 +57,7 @@ func (sc *runtimeDynamicImpl) Fixture() (*oasis.NetworkFixture, error) { staking.KindNodeKeyManager: *quantity.NewFromUint64(0), staking.KindRuntimeCompute: *quantity.NewFromUint64(1000), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(1000), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(0), }, }, } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/storage_early_state_sync.go b/go/oasis-test-runner/scenario/e2e/runtime/storage_early_state_sync.go index b142177e72b..ba2c6a6baa1 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/storage_early_state_sync.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/storage_early_state_sync.go @@ -57,6 +57,7 @@ func (sc *storageEarlyStateSyncImpl) Fixture() (*oasis.NetworkFixture, error) { staking.KindNodeKeyManager: *quantity.NewFromUint64(0), staking.KindRuntimeCompute: *quantity.NewFromUint64(1000), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(1000), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(0), }, }, } diff --git a/go/oasis-test-runner/scenario/e2e/runtime/txsource.go b/go/oasis-test-runner/scenario/e2e/runtime/txsource.go index 272db3500fc..5bce7d5badf 100644 --- a/go/oasis-test-runner/scenario/e2e/runtime/txsource.go +++ b/go/oasis-test-runner/scenario/e2e/runtime/txsource.go @@ -264,6 +264,7 @@ func (sc *txSourceImpl) Fixture() (*oasis.NetworkFixture, error) { staking.KindNodeKeyManager: *quantity.NewFromUint64(0), staking.KindRuntimeCompute: *quantity.NewFromUint64(100), staking.KindRuntimeKeyManager: *quantity.NewFromUint64(100), + staking.KindKeyManagerChurp: *quantity.NewFromUint64(0), }, }, TotalSupply: *quantity.NewFromUint64(150000001400), diff --git a/go/oasis-test-runner/scenario/e2e/upgrade.go b/go/oasis-test-runner/scenario/e2e/upgrade.go index adb586ec969..6e9247dba03 100644 --- a/go/oasis-test-runner/scenario/e2e/upgrade.go +++ b/go/oasis-test-runner/scenario/e2e/upgrade.go @@ -16,6 +16,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/cbor" "github.com/oasisprotocol/oasis-core/go/common/persistent" "github.com/oasisprotocol/oasis-core/go/common/pubsub" + "github.com/oasisprotocol/oasis-core/go/common/quantity" "github.com/oasisprotocol/oasis-core/go/common/version" consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" @@ -25,6 +26,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis/cli" "github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario" registry "github.com/oasisprotocol/oasis-core/go/registry/api" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" upgrade "github.com/oasisprotocol/oasis-core/go/upgrade/api" "github.com/oasisprotocol/oasis-core/go/upgrade/migrations" ) @@ -100,6 +102,19 @@ func (c *upgrade240Checker) PreUpgradeFn(ctx context.Context, ctrl *oasis.Contro return fmt.Errorf("key manager CHURP consensus parameters shouldn't be set: %w", err) } + // Check staking parameters. + stakeParams, err := ctrl.Staking.ConsensusParameters(ctx, consensus.HeightLatest) + if err != nil { + return fmt.Errorf("can't get staking consensus parameters: %w", err) + } + q, ok := stakeParams.Thresholds[staking.KindKeyManagerChurp] + if !ok { + return fmt.Errorf("key manager churp stake is not set") + } + if !q.IsZero() { + return fmt.Errorf("key manager churp stake not zero (expected: 0 actual: %s)", q) + } + // Check governance parameters. govParams, err := ctrl.Governance.ConsensusParameters(ctx, consensus.HeightLatest) if err != nil { @@ -152,6 +167,19 @@ func (c *upgrade240Checker) PostUpgradeFn(ctx context.Context, ctrl *oasis.Contr return fmt.Errorf("key manager CHURP consensus parameters are not default") } + // Check updated staking parameters. + stakeParams, err := ctrl.Staking.ConsensusParameters(ctx, consensus.HeightLatest) + if err != nil { + return fmt.Errorf("can't get staking consensus parameters: %w", err) + } + q, ok := stakeParams.Thresholds[staking.KindKeyManagerChurp] + if !ok { + return fmt.Errorf("key manager churp stake is not set") + } + if n := quantity.NewFromUint64(10_000_000_000_000); q.Cmp(n) != 0 { + return fmt.Errorf("key manager churp stake not updated correctly (expected: %s actual: %s)", n, q) + } + // Check updated governance parameters. govParams, err := ctrl.Governance.ConsensusParameters(ctx, consensus.HeightLatest) if err != nil { diff --git a/go/staking/api/api.go b/go/staking/api/api.go index 58580b75ca7..bd6fa085bfd 100644 --- a/go/staking/api/api.go +++ b/go/staking/api/api.go @@ -734,6 +734,7 @@ const ( KindNodeKeyManager ThresholdKind = 4 KindRuntimeCompute ThresholdKind = 5 KindRuntimeKeyManager ThresholdKind = 6 + KindKeyManagerChurp ThresholdKind = 7 KindEntityName = "entity" KindNodeValidatorName = "node-validator" @@ -742,6 +743,7 @@ const ( KindNodeKeyManagerName = "node-keymanager" KindRuntimeComputeName = "runtime-compute" KindRuntimeKeyManagerName = "runtime-keymanager" + KindKeyManagerChurpName = "keymanager-churp" ) // ThresholdKinds are the valid threshold kinds. @@ -753,6 +755,9 @@ var ThresholdKinds = []ThresholdKind{ KindNodeKeyManager, KindRuntimeCompute, KindRuntimeKeyManager, + // Commented out to pass sanity check of the genesis file and to avoid + // breaking consensus change parameters proposals for the staking module. + // KindKeyManagerChurp, } // String returns the string representation of a ThresholdKind. @@ -772,6 +777,8 @@ func (k ThresholdKind) String() string { return KindRuntimeComputeName case KindRuntimeKeyManager: return KindRuntimeKeyManagerName + case KindKeyManagerChurp: + return KindKeyManagerChurpName default: return "[unknown threshold kind]" } @@ -799,6 +806,8 @@ func (k *ThresholdKind) UnmarshalText(text []byte) error { *k = KindRuntimeCompute case KindRuntimeKeyManagerName: *k = KindRuntimeKeyManager + case KindKeyManagerChurpName: + *k = KindKeyManagerChurp default: return fmt.Errorf("%w: %s", ErrInvalidThreshold, string(text)) } @@ -1047,7 +1056,7 @@ func (e *EscrowAccount) CheckStakeClaims(tm map[ThresholdKind]quantity.Quantity) // AddStakeClaim attempts to add a stake claim to the given escrow account. // -// In case there is insufficient stake to cover the claim or an error occurrs, no modifications are +// In case there is insufficient stake to cover the claim or an error occurs, no modifications are // made to the stake accumulator. func (e *EscrowAccount) AddStakeClaim(tm map[ThresholdKind]quantity.Quantity, claim StakeClaim, thresholds []StakeThreshold) error { // Compute total amount of claims excluding the claim that we are just adding. This is needed diff --git a/go/staking/api/api_test.go b/go/staking/api/api_test.go index 059e9456fe2..d22c6ed192b 100644 --- a/go/staking/api/api_test.go +++ b/go/staking/api/api_test.go @@ -29,6 +29,7 @@ func TestConsensusParameters(t *testing.T) { KindNodeKeyManager: *quantity.NewQuantity(), KindRuntimeCompute: *quantity.NewQuantity(), KindRuntimeKeyManager: *quantity.NewQuantity(), + KindKeyManagerChurp: *quantity.NewQuantity(), } validThresholdsParams := ConsensusParameters{ Thresholds: validThresholds, @@ -210,6 +211,7 @@ func TestStakeAccumulator(t *testing.T) { KindNodeKeyManager: *quantity.NewFromUint64(50_000), KindRuntimeCompute: *quantity.NewFromUint64(2_000), KindRuntimeKeyManager: *quantity.NewFromUint64(1_000_000), + KindKeyManagerChurp: *quantity.NewFromUint64(500_000), } // Empty escrow account tests. diff --git a/go/staking/tests/state.go b/go/staking/tests/state.go index e3e9bbe4ca4..7f5f1eaf899 100644 --- a/go/staking/tests/state.go +++ b/go/staking/tests/state.go @@ -39,6 +39,7 @@ func GenesisState() api.Genesis { api.KindNodeKeyManager: *quantity.NewFromUint64(5), api.KindRuntimeCompute: *quantity.NewFromUint64(6), api.KindRuntimeKeyManager: *quantity.NewFromUint64(7), + api.KindKeyManagerChurp: *quantity.NewFromUint64(8), }, Slashing: map[api.SlashReason]api.Slash{ api.SlashConsensusEquivocation: { diff --git a/go/staking/tests/tester.go b/go/staking/tests/tester.go index 0aef99a6ca3..2313a8d27d2 100644 --- a/go/staking/tests/tester.go +++ b/go/staking/tests/tester.go @@ -201,6 +201,7 @@ func testThresholds(t *testing.T, _ *stakingTestsState, backend api.Backend, _ c api.KindNodeKeyManager, api.KindRuntimeCompute, api.KindRuntimeKeyManager, + api.KindKeyManagerChurp, } { qty, err := backend.Threshold(context.Background(), &api.ThresholdQuery{Kind: kind, Height: consensusAPI.HeightLatest}) require.NoError(err, "Threshold") diff --git a/go/upgrade/migrations/consensus_240.go b/go/upgrade/migrations/consensus_240.go index facdfba6c4d..438156a2c48 100644 --- a/go/upgrade/migrations/consensus_240.go +++ b/go/upgrade/migrations/consensus_240.go @@ -3,12 +3,15 @@ package migrations import ( "fmt" + "github.com/oasisprotocol/oasis-core/go/common/quantity" consensusState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/abci/state" abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api" governanceState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/governance/state" churpState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/keymanager/churp/state" registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state" + stakingState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/staking/state" "github.com/oasisprotocol/oasis-core/go/keymanager/churp" + staking "github.com/oasisprotocol/oasis-core/go/staking/api" ) const ( @@ -70,12 +73,25 @@ func (h *Handler240) ConsensusUpgrade(privateCtx interface{}) error { } // CHURP. - state := churpState.NewMutableState(abciCtx.State()) + churpState := churpState.NewMutableState(abciCtx.State()) - if err = state.SetConsensusParameters(abciCtx, &churp.DefaultConsensusParameters); err != nil { + if err = churpState.SetConsensusParameters(abciCtx, &churp.DefaultConsensusParameters); err != nil { return fmt.Errorf("failed to set CHURP consensus parameters: %w", err) } + // Staking. + stakeState := stakingState.NewMutableState(abciCtx.State()) + + stakeParams, err := stakeState.ConsensusParameters(abciCtx) + if err != nil { + return fmt.Errorf("failed to load staking consensus parameters: %w", err) + } + stakeParams.Thresholds[staking.KindKeyManagerChurp] = *quantity.NewFromUint64(10_000_000_000_000) + + if err = stakeState.SetConsensusParameters(abciCtx, stakeParams); err != nil { + return fmt.Errorf("failed to update staking consensus parameters: %w", err) + } + // Governance. govState := governanceState.NewMutableState(abciCtx.State()) diff --git a/runtime/src/transaction/dispatcher.rs b/runtime/src/transaction/dispatcher.rs index c2da134d1e0..cf03306b853 100644 --- a/runtime/src/transaction/dispatcher.rs +++ b/runtime/src/transaction/dispatcher.rs @@ -22,7 +22,7 @@ pub trait Dispatcher: Send + Sync { /// /// # Consensus Layer State Integrity /// - /// Before this method is invoked, consensus layer state integirty verification is performed. + /// Before this method is invoked, consensus layer state integrity verification is performed. fn execute_batch( &self, ctx: Context, @@ -37,7 +37,7 @@ pub trait Dispatcher: Send + Sync { /// /// # Consensus Layer State Integrity /// - /// Before this method is invoked, consensus layer state integirty verification is performed. + /// Before this method is invoked, consensus layer state integrity verification is performed. fn schedule_and_execute_batch( &self, _ctx: Context, From 1bd1d4da26dd77e4a703e5ce20d63f7941fa83f0 Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Wed, 24 Apr 2024 04:04:54 +0200 Subject: [PATCH 3/4] go/oasis-test-runner: Update mainnet genesis dump --- go/oasis-test-runner/scenario/e2e/genesis_file.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go/oasis-test-runner/scenario/e2e/genesis_file.go b/go/oasis-test-runner/scenario/e2e/genesis_file.go index 28e2ab0d711..9f19320ac80 100644 --- a/go/oasis-test-runner/scenario/e2e/genesis_file.go +++ b/go/oasis-test-runner/scenario/e2e/genesis_file.go @@ -21,13 +21,13 @@ import ( ) const ( - // Mainnet genesis dump at height: 11645601. - genesisURL = "https://oasis-artifacts.s3.eu-central-1.amazonaws.com/genesis_mainnet_dump_11645601.json" - genesisSHA256 = "16386902d822227d0ba1e011ab84a754a48c61457e06240986f9c00e84895459" // #nosec G101 + // Mainnet genesis dump at height: 16817956 (Eden upgrade). + genesisURL = "https://github.com/oasisprotocol/mainnet-artifacts/releases/download/2023-11-29/genesis.json" + genesisSHA256 = "b14e45e97da0216a16c25096fd216f591e4d526aa6abac110ac23cb327b64ba1" // #nosec G101 - genesisNeedsUpgrade = true + genesisNeedsUpgrade = false // Only relevant if genesis file doesn't need upgrade. - genesisDocumentHash = "b11b369e0da5bb230b220127f5e7b242d385ef8c6f54906243f30af63c815535" // #nosec G101 + genesisDocumentHash = "bb3d748def55bdfb797a2ac53ee6ee141e54cd2ab2dc2375f4a0703a178e6e55" // #nosec G101 ) // GenesisFile is the scenario for testing the correctness of marshalled genesis From 516d61eb66a4d4c6887bec6f9976b466cac89659 Mon Sep 17 00:00:00 2001 From: Peter Nose Date: Wed, 24 Apr 2024 03:41:22 +0200 Subject: [PATCH 4/4] go/staking: Move DebugBypassStake to staking parameters --- .../cometbft/apps/keymanager/churp/txs.go | 4 +- .../cometbft/apps/keymanager/keymanager.go | 3 +- .../cometbft/apps/registry/registry.go | 12 ++-- .../cometbft/apps/registry/transactions.go | 58 +++++++++++++++---- .../apps/supplementarysanity/checks.go | 10 +--- .../cometbft/tests/genesis/genesis.go | 1 - go/genesis/api/sanity_check.go | 3 + go/genesis/genesis_test.go | 7 +-- go/oasis-node/cmd/genesis/genesis.go | 9 +-- go/registry/api/api.go | 4 -- go/registry/api/sanity_check.go | 6 +- go/staking/api/api.go | 4 ++ go/staking/api/sanity_check.go | 7 +++ go/staking/tests/state.go | 1 + 14 files changed, 84 insertions(+), 45 deletions(-) diff --git a/go/consensus/cometbft/apps/keymanager/churp/txs.go b/go/consensus/cometbft/apps/keymanager/churp/txs.go index a01126e11d5..9119ea46f7c 100644 --- a/go/consensus/cometbft/apps/keymanager/churp/txs.go +++ b/go/consensus/cometbft/apps/keymanager/churp/txs.go @@ -430,9 +430,9 @@ func (ext *churpExt) computeNextHandoff(ctx *tmapi.Context) (beacon.EpochTime, e } func addStakeClaim(ctx *tmapi.Context, entityID signature.PublicKey, runtimeID common.Namespace, churpID uint8) error { - regState := registryState.NewMutableState(ctx.State()) + stakeState := stakingState.NewMutableState(ctx.State()) - regParams, err := regState.ConsensusParameters(ctx) + regParams, err := stakeState.ConsensusParameters(ctx) if err != nil { return err } diff --git a/go/consensus/cometbft/apps/keymanager/keymanager.go b/go/consensus/cometbft/apps/keymanager/keymanager.go index 0507bbadc4d..4009dd3907d 100644 --- a/go/consensus/cometbft/apps/keymanager/keymanager.go +++ b/go/consensus/cometbft/apps/keymanager/keymanager.go @@ -105,8 +105,9 @@ func (app *keymanagerApplication) EndBlock(*tmapi.Context) (types.ResponseEndBlo // to cover the entity and runtime deposits. func suspendRuntimes(ctx *tmapi.Context) error { regState := registryState.NewMutableState(ctx.State()) + stakeState := stakingState.NewMutableState(ctx.State()) - params, err := regState.ConsensusParameters(ctx) + params, err := stakeState.ConsensusParameters(ctx) if err != nil { return fmt.Errorf("failed to get consensus parameters: %w", err) } diff --git a/go/consensus/cometbft/apps/registry/registry.go b/go/consensus/cometbft/apps/registry/registry.go index c4d5ff875e0..a209ed75725 100644 --- a/go/consensus/cometbft/apps/registry/registry.go +++ b/go/consensus/cometbft/apps/registry/registry.go @@ -158,10 +158,10 @@ func (app *registryApplication) EndBlock(*api.Context) (types.ResponseEndBlock, } func (app *registryApplication) onRegistryEpochChanged(ctx *api.Context, registryEpoch beacon.EpochTime) (err error) { - state := registryState.NewMutableState(ctx.State()) + regState := registryState.NewMutableState(ctx.State()) stakeState := stakingState.NewMutableState(ctx.State()) - nodes, err := state.Nodes(ctx) + nodes, err := regState.Nodes(ctx) if err != nil { ctx.Logger().Error("onRegistryEpochChanged: failed to get nodes", "err", err, @@ -177,7 +177,7 @@ func (app *registryApplication) onRegistryEpochChanged(ctx *api.Context, registr return fmt.Errorf("registry: onRegistryEpochChanged: failed to get debonding interval: %w", err) } - params, err := state.ConsensusParameters(ctx) + params, err := stakeState.ConsensusParameters(ctx) if err != nil { ctx.Logger().Error("onRegistryEpochChanged: failed to fetch consensus parameters", "err", err, @@ -207,7 +207,7 @@ func (app *registryApplication) onRegistryEpochChanged(ctx *api.Context, registr // node expiration (this is required so that we don't emit expiration // events every epoch). var status *registry.NodeStatus - status, err = state.NodeStatus(ctx, node.ID) + status, err = regState.NodeStatus(ctx, node.ID) if err != nil { return fmt.Errorf("registry: onRegistryEpochChanged: couldn't get node status: %w", err) } @@ -215,7 +215,7 @@ func (app *registryApplication) onRegistryEpochChanged(ctx *api.Context, registr if !status.ExpirationProcessed { expiredNodes = append(expiredNodes, node) status.ExpirationProcessed = true - if err = state.SetNodeStatus(ctx, node.ID, status); err != nil { + if err = regState.SetNodeStatus(ctx, node.ID, status); err != nil { return fmt.Errorf("registry: onRegistryEpochChanged: couldn't set node status: %w", err) } } @@ -229,7 +229,7 @@ func (app *registryApplication) onRegistryEpochChanged(ctx *api.Context, registr ctx.Logger().Debug("removing expired node", "node_id", node.ID, ) - if err = state.RemoveNode(ctx, node); err != nil { + if err = regState.RemoveNode(ctx, node); err != nil { return fmt.Errorf("registry: onRegistryEpochChanged: couldn't remove node: %w", err) } diff --git a/go/consensus/cometbft/apps/registry/transactions.go b/go/consensus/cometbft/apps/registry/transactions.go index fe46ea64843..68f579594ca 100644 --- a/go/consensus/cometbft/apps/registry/transactions.go +++ b/go/consensus/cometbft/apps/registry/transactions.go @@ -33,7 +33,7 @@ func (app *registryApplication) registerEntity( // Charge gas for this transaction. params, err := state.ConsensusParameters(ctx) if err != nil { - ctx.Logger().Error("RegisterEntity: failed to fetch consensus parameters", + ctx.Logger().Error("RegisterEntity: failed to fetch registry consensus parameters", "err", err, ) return err @@ -57,7 +57,16 @@ func (app *registryApplication) registerEntity( return registry.ErrIncorrectTxSigner } - if !params.DebugBypassStake { + stakeState := stakingState.NewMutableState(ctx.State()) + stakeParams, err := stakeState.ConsensusParameters(ctx) + if err != nil { + ctx.Logger().Error("RegisterEntity: failed to fetch staking consensus parameters", + "err", err, + ) + return err + } + + if !stakeParams.DebugBypassStake { acctAddr := staking.NewAddress(ent.ID) if err = stakingState.AddStakeClaim( ctx, @@ -95,7 +104,7 @@ func (app *registryApplication) deregisterEntity(ctx *api.Context, state *regist // Charge gas for this transaction. params, err := state.ConsensusParameters(ctx) if err != nil { - ctx.Logger().Error("DeregisterEntity: failed to fetch consensus parameters", + ctx.Logger().Error("DeregisterEntity: failed to fetch registry consensus parameters", "err", err, ) return err @@ -149,7 +158,16 @@ func (app *registryApplication) deregisterEntity(ctx *api.Context, state *regist return fmt.Errorf("DeregisterEntity: failed to remove entity: %w", err) } - if !params.DebugBypassStake { + stakeState := stakingState.NewMutableState(ctx.State()) + stakeParams, err := stakeState.ConsensusParameters(ctx) + if err != nil { + ctx.Logger().Error("DeregisterEntity: failed to fetch staking consensus parameters", + "err", err, + ) + return err + } + + if !stakeParams.DebugBypassStake { acctAddr := staking.NewAddress(id) if err = stakingState.RemoveStakeClaim(ctx, acctAddr, registry.StakeClaimRegisterEntity); err != nil { panic(fmt.Errorf("DeregisterEntity: failed to remove stake claim: %w", err)) @@ -194,7 +212,7 @@ func (app *registryApplication) registerNode( // nolint: gocyclo params, err := state.ConsensusParameters(ctx) if err != nil { - ctx.Logger().Error("RegisterNode: failed to fetch consensus parameters", + ctx.Logger().Error("RegisterNode: failed to fetch registry consensus parameters", "err", err, ) return err @@ -299,8 +317,17 @@ func (app *registryApplication) registerNode( // nolint: gocyclo defer ctx.Close() // Check that the entity has enough stake for this node registration. + stakeState := stakingState.NewMutableState(ctx.State()) + stakeParams, err := stakeState.ConsensusParameters(ctx) + if err != nil { + ctx.Logger().Error("RegisterNode: failed to fetch staking consensus parameters", + "err", err, + ) + return err + } + var stakeAcc *stakingState.StakeAccumulatorCache - if !params.DebugBypassStake { + if !stakeParams.DebugBypassStake { stakeAcc, err = stakingState.NewStakeAccumulatorCache(ctx) if err != nil { return fmt.Errorf("failed to create stake accumulator cache: %w", err) @@ -400,7 +427,7 @@ func (app *registryApplication) registerNode( // nolint: gocyclo for _, rt := range paidRuntimes { // Only resume a runtime if the entity has enough stake to avoid having the runtime be // suspended again on the next epoch transition. - if !params.DebugBypassStake && rt.GovernanceModel != registry.GovernanceConsensus { + if !stakeParams.DebugBypassStake && rt.GovernanceModel != registry.GovernanceConsensus { acctAddr := rt.StakingAddress() if acctAddr == nil { // This should never happen. @@ -467,7 +494,7 @@ func (app *registryApplication) unfreezeNode( // Charge gas for this transaction. params, err := state.ConsensusParameters(ctx) if err != nil { - ctx.Logger().Error("UnfreezeNode: failed to fetch consensus parameters", + ctx.Logger().Error("UnfreezeNode: failed to fetch registry consensus parameters", "err", err, ) return err @@ -537,7 +564,7 @@ func (app *registryApplication) registerRuntime( // nolint: gocyclo ) (*registry.Runtime, error) { params, err := state.ConsensusParameters(ctx) if err != nil { - ctx.Logger().Error("RegisterRuntime: failed to fetch consensus parameters", + ctx.Logger().Error("RegisterRuntime: failed to fetch registry consensus parameters", "err", err, ) return nil, err @@ -658,7 +685,16 @@ func (app *registryApplication) registerRuntime( // nolint: gocyclo // Make sure that the entity or runtime has enough stake. // Runtimes using the consensus layer governance model do not require stake. - if rtAddress := rt.StakingAddress(); !params.DebugBypassStake && rtAddress != nil { + stakeState := stakingState.NewMutableState(ctx.State()) + stakeParams, err := stakeState.ConsensusParameters(ctx) + if err != nil { + ctx.Logger().Error("RegisterRuntime: failed to fetch staking consensus parameters", + "err", err, + ) + return nil, err + } + + if rtAddress := rt.StakingAddress(); !stakeParams.DebugBypassStake && rtAddress != nil { claim := registry.StakeClaimForRuntime(rt.ID) thresholds := registry.StakeThresholdsForRuntime(rt) @@ -730,7 +766,7 @@ func (app *registryApplication) proveFreshness( ) error { params, err := state.ConsensusParameters(ctx) if err != nil { - ctx.Logger().Error("ProveFreshness: failed to fetch consensus parameters", + ctx.Logger().Error("ProveFreshness: failed to fetch registry consensus parameters", "err", err, ) return err diff --git a/go/consensus/cometbft/apps/supplementarysanity/checks.go b/go/consensus/cometbft/apps/supplementarysanity/checks.go index c98e28f33ac..f5310d1ca63 100644 --- a/go/consensus/cometbft/apps/supplementarysanity/checks.go +++ b/go/consensus/cometbft/apps/supplementarysanity/checks.go @@ -378,17 +378,13 @@ func checkStakeClaims(ctx *abciAPI.Context, _ beacon.EpochTime) error { stakingSt := stakingState.NewMutableState(ctx.State()) churpSt := churpState.NewMutableState(ctx.State()) - regParams, err := regSt.ConsensusParameters(ctx) - if err != nil { - return fmt.Errorf("failed to get registry consensus parameters: %w", err) - } - stakingParams, err := stakingSt.ConsensusParameters(ctx) + params, err := stakingSt.ConsensusParameters(ctx) if err != nil { return fmt.Errorf("failed to get staking consensus parameters: %w", err) } // Skip checks if stake is being bypassed. - if regParams.DebugBypassStake { + if params.DebugBypassStake { return nil } @@ -434,5 +430,5 @@ func checkStakeClaims(ctx *abciAPI.Context, _ beacon.EpochTime) error { return err } - return staking.SanityCheckStake(accounts, escrows, stakingParams.Thresholds, false) + return staking.SanityCheckStake(accounts, escrows, params.Thresholds, false) } diff --git a/go/consensus/cometbft/tests/genesis/genesis.go b/go/consensus/cometbft/tests/genesis/genesis.go index 65ca352051a..255b9cf2072 100644 --- a/go/consensus/cometbft/tests/genesis/genesis.go +++ b/go/consensus/cometbft/tests/genesis/genesis.go @@ -67,7 +67,6 @@ func NewTestNodeGenesisProvider(identity *identity.Identity, ent *entity.Entity, Parameters: registry.ConsensusParameters{ DebugAllowUnroutableAddresses: true, DebugAllowTestRuntimes: true, - DebugBypassStake: true, DebugDeployImmediately: true, EnableRuntimeGovernanceModels: map[registry.RuntimeGovernanceModel]bool{ registry.GovernanceEntity: true, diff --git a/go/genesis/api/sanity_check.go b/go/genesis/api/sanity_check.go index 5f6b265c52f..45134d0817d 100644 --- a/go/genesis/api/sanity_check.go +++ b/go/genesis/api/sanity_check.go @@ -58,5 +58,8 @@ func (d *Document) SanityCheck() error { return err } + if d.Staking.Parameters.DebugBypassStake { + return nil + } return staking.SanityCheckStake(d.Staking.Ledger, escrows, d.Staking.Parameters.Thresholds, true) } diff --git a/go/genesis/genesis_test.go b/go/genesis/genesis_test.go index 255cffd0ea1..3e63657d0ea 100644 --- a/go/genesis/genesis_test.go +++ b/go/genesis/genesis_test.go @@ -55,7 +55,6 @@ func testDoc() genesis.Document { Registry: registry.Genesis{ Parameters: registry.ConsensusParameters{ DebugAllowUnroutableAddresses: true, - DebugBypassStake: true, EnableRuntimeGovernanceModels: map[registry.RuntimeGovernanceModel]bool{ registry.GovernanceEntity: true, registry.GovernanceRuntime: true, @@ -136,7 +135,7 @@ func TestGenesisChainContext(t *testing.T) { // Having to update this every single time the genesis structure // changes isn't annoying at all. - require.Equal(t, "a9dfaa6890772f10bde7b0c34214b1faf22fdecd334d2199f30414f3a30aa3f6", stableDoc.ChainContext()) + require.Equal(t, "707019a3f189ebd805e8bd370d772e9b3cce7e51b2052947e9ab486f2eb77bb1", stableDoc.ChainContext()) } func TestGenesisSanityCheck(t *testing.T) { @@ -912,12 +911,12 @@ func TestGenesisSanityCheck(t *testing.T) { // Sanity check entity stake claims. d = testDoc() - // Enable stake claims check. - d.Registry.Parameters.DebugBypassStake = false // Setup registry state. d.Registry.Entities = []*entity.SignedEntity{signedEntityWithTestNode} d.Registry.Runtimes = []*registry.Runtime{testKMRuntime, testRuntime} d.Registry.Nodes = []*node.MultiSignedNode{signedComputeTestNode} + // Enable stake claims check. + d.Staking.Parameters.DebugBypassStake = false // Setup ledger. d.Staking.Ledger[testEntityAddress] = &staking.Account{ Escrow: staking.EscrowAccount{ diff --git a/go/oasis-node/cmd/genesis/genesis.go b/go/oasis-node/cmd/genesis/genesis.go index cf6ba4846a5..73dd79d036e 100644 --- a/go/oasis-node/cmd/genesis/genesis.go +++ b/go/oasis-node/cmd/genesis/genesis.go @@ -59,7 +59,6 @@ const ( CfgRegistryDisableRuntimeRegistration = "registry.disable_runtime_registration" CfgRegistryDebugAllowUnroutableAddresses = "registry.debug.allow_unroutable_addresses" CfgRegistryDebugAllowTestRuntimes = "registry.debug.allow_test_runtimes" - cfgRegistryDebugBypassStake = "registry.debug.bypass_stake" // nolint: gosec CfgRegistryEnableRuntimeGovernanceModels = "registry.enable_runtime_governance_models" CfgRegistryEnableKeyManagerCHURP = "registry.enable_key_manager_churp" CfgRegistryTEEFeaturesSGXPCS = "registry.tee_features.sgx.pcs" @@ -101,6 +100,7 @@ const ( // Staking config flags. CfgStakingTokenSymbol = "staking.token_symbol" CfgStakingTokenValueExponent = "staking.token_value_exponent" + cfgStakingDebugBypassStake = "staking.debug.bypass_stake" // nolint: gosec // CometBFT config flags. CfgConsensusTimeoutCommit = "consensus.cometbft.timeout_commit" @@ -336,7 +336,6 @@ func AppendRegistryState(doc *genesis.Document, entities, runtimes, nodes []stri Parameters: registry.ConsensusParameters{ DebugAllowUnroutableAddresses: viper.GetBool(CfgRegistryDebugAllowUnroutableAddresses), DebugAllowTestRuntimes: viper.GetBool(CfgRegistryDebugAllowTestRuntimes), - DebugBypassStake: viper.GetBool(cfgRegistryDebugBypassStake), GasCosts: registry.DefaultGasCosts, // TODO: Make these configurable. MaxNodeExpiration: viper.GetUint64(CfgRegistryMaxNodeExpiration), DisableRuntimeRegistration: viper.GetBool(CfgRegistryDisableRuntimeRegistration), @@ -627,6 +626,8 @@ func appendStakingState(doc *genesis.Document, statePath string) error { st.State.TokenValueExponent = tokenValueExponent } + st.State.Parameters.DebugBypassStake = viper.GetBool(cfgStakingDebugBypassStake) + return st.AppendTo(doc) } @@ -797,7 +798,6 @@ func init() { initGenesisFlags.Bool(CfgRegistryDisableRuntimeRegistration, false, "disable non-genesis runtime registration") initGenesisFlags.Bool(CfgRegistryDebugAllowUnroutableAddresses, false, "allow unroutable addreses (UNSAFE)") initGenesisFlags.Bool(CfgRegistryDebugAllowTestRuntimes, false, "enable test runtime registration") - initGenesisFlags.Bool(cfgRegistryDebugBypassStake, false, "bypass all stake checks and operations (UNSAFE)") initGenesisFlags.StringSlice(CfgRegistryEnableRuntimeGovernanceModels, []string{"entity"}, "set of enabled runtime governance models") initGenesisFlags.Bool(CfgRegistryEnableKeyManagerCHURP, false, "enable key manager CHURP extension") initGenesisFlags.Bool(CfgRegistryTEEFeaturesSGXPCS, true, "enable PCS support for SGX TEEs") @@ -806,7 +806,6 @@ func init() { initGenesisFlags.Bool(CfgRegistryTEEFeaturesFreshnessProofs, true, "enable freshness proofs") _ = initGenesisFlags.MarkHidden(CfgRegistryDebugAllowUnroutableAddresses) _ = initGenesisFlags.MarkHidden(CfgRegistryDebugAllowTestRuntimes) - _ = initGenesisFlags.MarkHidden(cfgRegistryDebugBypassStake) // Scheduler config flags. initGenesisFlags.Int(cfgSchedulerMinValidators, 1, "minimum number of validators") @@ -848,6 +847,8 @@ func init() { // Staking config flags. initGenesisFlags.String(CfgStakingTokenSymbol, "", "token's ticker symbol") initGenesisFlags.Uint8(CfgStakingTokenValueExponent, 0, "token value's base-10 exponent") + initGenesisFlags.Bool(cfgStakingDebugBypassStake, false, "bypass all stake checks and operations (UNSAFE)") + _ = initGenesisFlags.MarkHidden(cfgStakingDebugBypassStake) // CometBFT config flags. initGenesisFlags.Duration(CfgConsensusTimeoutCommit, 1*time.Second, "cometbft commit timeout") diff --git a/go/registry/api/api.go b/go/registry/api/api.go index 076970f64b0..c13b5a90f84 100644 --- a/go/registry/api/api.go +++ b/go/registry/api/api.go @@ -1387,10 +1387,6 @@ type ConsensusParameters struct { // be registered. DebugAllowTestRuntimes bool `json:"debug_allow_test_runtimes,omitempty"` - // DebugBypassStake is true iff the registry should bypass all of the staking - // related checks and operations. - DebugBypassStake bool `json:"debug_bypass_stake,omitempty"` - // DebugDeployImmediately is true iff runtime registrations should // allow immediate deployment. DebugDeployImmediately bool `json:"debug_deploy_immediately,omitempty"` diff --git a/go/registry/api/sanity_check.go b/go/registry/api/sanity_check.go index ab31f1ce352..bbedc2d0c92 100644 --- a/go/registry/api/sanity_check.go +++ b/go/registry/api/sanity_check.go @@ -18,7 +18,7 @@ import ( // SanityCheck performs a sanity check on the consensus parameters. func (p *ConsensusParameters) SanityCheck() error { if !flags.DebugDontBlameOasis() { - if p.DebugAllowUnroutableAddresses || p.DebugBypassStake || p.DebugDeployImmediately { + if p.DebugAllowUnroutableAddresses || p.DebugDeployImmediately { return fmt.Errorf("one or more unsafe debug flags set") } if p.MaxNodeExpiration == 0 { @@ -97,10 +97,6 @@ func (g *Genesis) SanityCheck( } // Add stake claims. - if g.Parameters.DebugBypassStake { - return nil - } - nodes, err := nodeLookup.Nodes(context.Background()) if err != nil { return fmt.Errorf("registry: sanity check failed: could not obtain node list from nodeLookup: %w", err) diff --git a/go/staking/api/api.go b/go/staking/api/api.go index bd6fa085bfd..660e64431b7 100644 --- a/go/staking/api/api.go +++ b/go/staking/api/api.go @@ -1235,6 +1235,10 @@ type ConsensusParameters struct { // nolint: maligned // RewardFactorBlockProposed is the factor for a reward distributed per block // to the entity that proposed the block. RewardFactorBlockProposed quantity.Quantity `json:"reward_factor_block_proposed"` + + // DebugBypassStake is true iff all of the staking-related checks and + // operations should be bypassed. + DebugBypassStake bool `json:"debug_bypass_stake,omitempty"` } // ConsensusParameterChanges are allowed staking consensus parameter changes. diff --git a/go/staking/api/sanity_check.go b/go/staking/api/sanity_check.go index 2e7b71937c1..6e3b5b0f34f 100644 --- a/go/staking/api/sanity_check.go +++ b/go/staking/api/sanity_check.go @@ -7,11 +7,18 @@ import ( beacon "github.com/oasisprotocol/oasis-core/go/beacon/api" "github.com/oasisprotocol/oasis-core/go/common/quantity" + "github.com/oasisprotocol/oasis-core/go/oasis-node/cmd/common/flags" "github.com/oasisprotocol/oasis-core/go/staking/api/token" ) // SanityCheck performs a sanity check on the consensus parameters. func (p *ConsensusParameters) SanityCheck() error { + if !flags.DebugDontBlameOasis() { + if p.DebugBypassStake { + return fmt.Errorf("one or more unsafe debug flags set") + } + } + // Thresholds. for _, kind := range ThresholdKinds { val, ok := p.Thresholds[kind] diff --git a/go/staking/tests/state.go b/go/staking/tests/state.go index 7f5f1eaf899..68937d0e68d 100644 --- a/go/staking/tests/state.go +++ b/go/staking/tests/state.go @@ -54,6 +54,7 @@ func GenesisState() api.Genesis { FeeSplitWeightVote: *quantity.NewFromUint64(1), RewardFactorEpochSigned: *quantity.NewFromUint64(1), // Zero RewardFactorBlockProposed is normal. + DebugBypassStake: true, }, TokenSymbol: "TEST", TotalSupply: *quantity.NewFromUint64(math.MaxInt64),