Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b22376b
go/common/node/node: Replace uint64 with BeaconTime
peternose Sep 26, 2025
329c386
go/consensus/cometbft/apps/scheduler: Move elections to a method
peternose Sep 29, 2025
be49ff2
go/registry/api/status: Add method IsEligibleForElection
peternose Sep 29, 2025
7a8a53f
go/registry/api/status: Add Freeze method to node status
peternose Sep 27, 2025
9d5e9e5
go/consensus/cometbft/apps/scheduler: Add method for committee elections
peternose Sep 29, 2025
300aa9e
go/consensus/cometbft/apps/scheduler: Add method for reward distribution
peternose Sep 29, 2025
3128aac
go/consensus/cometbft/apps/scheduler: Change entity map to a set
peternose Sep 29, 2025
d0161f1
go/consensus/cometbft/apps/scheduler: Remove unused parameter
peternose Sep 29, 2025
d8b1760
go/consensus/cometbft/apps/scheduler: Always use stake accumulator cache
peternose Sep 29, 2025
0e81b33
go/consensus/cometbft/apps/scheduler: Minor improvements
peternose Sep 26, 2025
472c312
go/consensus/cometbft/apps/scheduler/shuffle: Breaking change bug
peternose Sep 26, 2025
851b921
go/consensus/cometbft/apps/scheduler: Add epoch to shuffle method
peternose Sep 26, 2025
9a21733
go/consensus/cometbft/apps/scheduler: Improve balance fetching
peternose Sep 26, 2025
4564632
go/consensus/cometbft/apps/scheduler: Initialize RNG in one place
peternose Sep 26, 2025
33edfb5
go/consensus/cometbft/apps/scheduler: Remove unused app receiver
peternose Sep 29, 2025
e273fe9
go/consensus/cometbft/apps/scheduler: Pass entropy to methods
peternose Sep 28, 2025
7269001
go/consensus/cometbft/apps/scheduler: Pass VRF previous state to methods
peternose Sep 28, 2025
5f45e72
go/consensus/cometbft/apps/scheduler: Move diffValidators function
peternose Sep 29, 2025
a03d68c
go/consensus/cometbft/apps/scheduler: Use right VRF state at epoch end
peternose Sep 29, 2025
457243f
go/consensus/cometbft/apps/scheduler: Move validator updates to a method
peternose Sep 29, 2025
de40c4f
go/consensus/cometbft/apps/scheduler: Run elections at epoch end
peternose Sep 29, 2025
11f51af
go/oasis-test-runner/scenario/e2e/runtime/byzantine: Fix epoch transi…
peternose Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/6338.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/consensus/cometbft/apps/scheduler: Run elections at epoch end
2 changes: 1 addition & 1 deletion go/beacon/tests/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func MustAdvanceEpochMulti(t *testing.T, consensus consensusAPI.Service, increme
if !nd.Node.HasRoles(node.RoleValidator) {
continue
}
if nd.Node.Expiration > uint64(epoch+1) {
if nd.Node.Expiration > epoch+1 {
break EVENTS
}
case <-time.After(recvTimeout):
Expand Down
7 changes: 4 additions & 3 deletions go/common/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/oasisprotocol/curve25519-voi/primitives/x25519"

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/crypto/hash"
Expand Down Expand Up @@ -77,7 +78,7 @@ type Node struct {
EntityID signature.PublicKey `json:"entity_id"`

// Expiration is the epoch in which this node's commitment expires.
Expiration uint64 `json:"expiration"`
Expiration beacon.EpochTime `json:"expiration"`

// TLS contains information for connecting to this node via TLS.
TLS TLSInfo `json:"tls"`
Expand Down Expand Up @@ -116,7 +117,7 @@ type nodeV2 struct {
EntityID signature.PublicKey `json:"entity_id"`

// Expiration is the epoch in which this node's commitment expires.
Expiration uint64 `json:"expiration"`
Expiration beacon.EpochTime `json:"expiration"`

// TLS contains information for connecting to this node via TLS.
TLS nodeV2TLSInfo `json:"tls"`
Expand Down Expand Up @@ -385,7 +386,7 @@ func (n *Node) OnlyHasRoles(r RolesMask) bool {

// IsExpired returns true if the node expiration epoch is strictly smaller
// than the passed (current) epoch.
func (n *Node) IsExpired(epoch uint64) bool {
func (n *Node) IsExpired(epoch beacon.EpochTime) bool {
return n.Expiration < epoch
}

Expand Down
2 changes: 1 addition & 1 deletion go/consensus/cometbft/api/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func convertValidators(d *genesis.Document) ([]cmttypes.GenesisValidator, error)
}

// Skip expired nodes.
if openedNode.IsExpired(uint64(d.Beacon.Base)) {
if openedNode.IsExpired(d.Beacon.Base) {
continue
}

Expand Down
4 changes: 2 additions & 2 deletions go/consensus/cometbft/apps/beacon/backend_vrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ func (impl *backendVRF) OnBeginBlock(
return fmt.Errorf("beacon: timekeeping broken")
}

var pendingMockEpoch *beacon.EpochTime
if pendingMockEpoch, err = state.PendingMockEpoch(ctx); err != nil {
pendingMockEpoch, err := state.PendingMockEpoch(ctx)
if err != nil {
return fmt.Errorf("beacon: failed to query mock epoch state: %w", err)
}
if pendingMockEpoch == nil {
Expand Down
2 changes: 1 addition & 1 deletion go/consensus/cometbft/apps/keymanager/churp/txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ func runtimeAttestationKey(ctx *tmapi.Context, nodeID signature.PublicKey, now b
if err != nil {
return nil, err
}
if n.IsExpired(uint64(now)) {
if n.IsExpired(now) {
return nil, fmt.Errorf("keymanager: churp: node registration expired")
}
if !n.HasRoles(node.RoleKeyManager) {
Expand Down
2 changes: 1 addition & 1 deletion go/consensus/cometbft/apps/keymanager/secrets/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func generateStatus( // nolint: gocyclo
// to the key manager status fields.
nextNode:
for _, n := range nodes {
if n.IsExpired(uint64(epoch)) {
if n.IsExpired(epoch) {
continue
}
if !n.HasRoles(node.RoleKeyManager) {
Expand Down
20 changes: 10 additions & 10 deletions go/consensus/cometbft/apps/keymanager/secrets/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,70 +135,70 @@ func TestGenerateStatus(t *testing.T) {
// Validator node.
{
ID: memorySigner.NewTestSigner("node 0").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleValidator,
Runtimes: nodeRuntimes[0:1],
},
// Expired.
{
ID: memorySigner.NewTestSigner("node 1").Public(),
Expiration: uint64(epoch) - 1,
Expiration: epoch - 1,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[0:1],
},
// No runtimes.
{
ID: memorySigner.NewTestSigner("node 2").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: []*node.Runtime{},
},
// Compute runtime.
{
ID: memorySigner.NewTestSigner("node 3").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[6:7],
},
// The second key manager.
{
ID: memorySigner.NewTestSigner("node 4").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[4:6],
},
// One key manager, incompatible versions.
{
ID: memorySigner.NewTestSigner("node 5").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[0:4],
},
// One key manager, one version (secure = false).
{
ID: memorySigner.NewTestSigner("node 6").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[0:1],
},
// One key manager, two versions (secure = true).
{
ID: memorySigner.NewTestSigner("node 7").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[1:2],
},
// One key manager, two versions (secure = true).
{
ID: memorySigner.NewTestSigner("node 8").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[2:4],
},
// Two key managers, two versions.
{
ID: memorySigner.NewTestSigner("node 9").Public(),
Expiration: uint64(epoch),
Expiration: epoch,
Roles: node.RoleKeyManager,
Runtimes: nodeRuntimes[2:6],
},
Expand Down
5 changes: 3 additions & 2 deletions go/consensus/cometbft/apps/registry/messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/stretchr/testify/require"

beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
abciAPI "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/api"
registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state"
Expand All @@ -30,7 +31,7 @@ func TestChangeParameters(t *testing.T) {
require.NoError(t, err, "setting consensus parameters should succeed")

// Prepare proposal.
maxNodeExpiration := uint64(20)
maxNodeExpiration := beacon.EpochTime(20)
changes := registry.ConsensusParameterChanges{
MaxNodeExpiration: &maxNodeExpiration,
}
Expand Down Expand Up @@ -90,7 +91,7 @@ func TestChangeParameters(t *testing.T) {
t.Run("invalid changes", func(t *testing.T) {
require := require.New(t)

var maxNodeExpiration uint64
var maxNodeExpiration beacon.EpochTime
changes := registry.ConsensusParameterChanges{
MaxNodeExpiration: &maxNodeExpiration,
}
Expand Down
4 changes: 2 additions & 2 deletions go/consensus/cometbft/apps/registry/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (q *Query) Node(ctx context.Context, id signature.PublicKey) (*node.Node, e
}

// Do not return expired nodes.
if node.IsExpired(uint64(epoch)) {
if node.IsExpired(epoch) {
return nil, registry.ErrNoSuchNode
}
return node, nil
Expand Down Expand Up @@ -81,7 +81,7 @@ func (q *Query) Nodes(ctx context.Context) ([]*node.Node, error) {
// Filter out expired nodes.
var filteredNodes []*node.Node
for _, n := range nodes {
if n.IsExpired(uint64(epoch)) {
if n.IsExpired(epoch) {
continue
}
filteredNodes = append(filteredNodes, n)
Expand Down
6 changes: 3 additions & 3 deletions go/consensus/cometbft/apps/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (app *Application) onRegistryEpochChanged(ctx *api.Context, registryEpoch b
// otherwise the nodes could not be resolved.
var expiredNodes []*node.Node
for _, node := range nodes {
if !node.IsExpired(uint64(registryEpoch)) {
if !node.IsExpired(registryEpoch) {
continue
}

Expand All @@ -234,11 +234,11 @@ func (app *Application) onRegistryEpochChanged(ctx *api.Context, registryEpoch b
}

// If node has been expired for the debonding interval, finally remove it.
if math.MaxUint64-node.Expiration < uint64(debondingInterval) {
if math.MaxUint64-node.Expiration < debondingInterval {
// Overflow, the node will never be removed.
continue
}
if beacon.EpochTime(node.Expiration)+debondingInterval < registryEpoch {
if node.Expiration+debondingInterval < registryEpoch {
ctx.Logger().Debug("removing expired node",
"node_id", node.ID,
)
Expand Down
12 changes: 6 additions & 6 deletions go/consensus/cometbft/apps/registry/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,23 +266,23 @@ func (app *Application) registerNode( // nolint: gocyclo
// immediately expire.
//
// Yes, this is duplicated. Blame the sanity checker.
if !ctx.IsInitChain() && newNode.Expiration <= uint64(epoch) {
if !ctx.IsInitChain() && newNode.Expiration <= epoch {
ctx.Logger().Debug("RegisterNode: node descriptor is expired",
"new_node", newNode,
"epoch", epoch,
)
return registry.ErrNodeExpired
}

var additionalEpochs uint64
if newNode.Expiration > uint64(epoch) {
additionalEpochs = newNode.Expiration - uint64(epoch)
var additionalEpochs beacon.EpochTime
if newNode.Expiration > epoch {
additionalEpochs = newNode.Expiration - epoch
}

// Check if node exists.
existingNode, err := state.Node(ctx, newNode.ID)
isNewNode := err == registry.ErrNoSuchNode
isExpiredNode := err == nil && existingNode.IsExpired(uint64(epoch))
isExpiredNode := err == nil && existingNode.IsExpired(epoch)
if !isNewNode && err != nil {
// Something went horribly wrong, and we failed to query the node.
ctx.Logger().Error("RegisterNode: failed to query node",
Expand All @@ -300,7 +300,7 @@ func (app *Application) registerNode( // nolint: gocyclo
// Remaining epochs are credited so the node doesn't end up paying twice.
// NOTE: This assumes that changing runtimes is not allowed as otherwise we
// would need to account this per-runtime.
remainingEpochs := existingNode.Expiration - uint64(epoch)
remainingEpochs := existingNode.Expiration - epoch
if additionalEpochs > remainingEpochs {
additionalEpochs = additionalEpochs - remainingEpochs
} else {
Expand Down
4 changes: 2 additions & 2 deletions go/consensus/cometbft/apps/roothash/liveness.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ func processLivenessStatistics(ctx *tmapi.Context, epoch beacon.EpochTime, rtSta
if fault.Failures >= maxFailures {
// Make sure to freeze forever if this would otherwise overflow.
if epoch > registry.FreezeForever-slashParams.FreezeInterval {
status.FreezeEndTime = registry.FreezeForever
status.Freeze(registry.FreezeForever)
} else {
status.FreezeEndTime = epoch + slashParams.FreezeInterval
status.Freeze(epoch + slashParams.FreezeInterval)
}

// Slash if configured.
Expand Down
19 changes: 19 additions & 0 deletions go/consensus/cometbft/apps/roothash/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (
registryState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/registry/state"
roothashApi "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/api"
roothashState "github.com/oasisprotocol/oasis-core/go/consensus/cometbft/apps/roothash/state"
"github.com/oasisprotocol/oasis-core/go/consensus/cometbft/features"
governance "github.com/oasisprotocol/oasis-core/go/governance/api"
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
"github.com/oasisprotocol/oasis-core/go/roothash/api/message"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
"github.com/oasisprotocol/oasis-core/go/upgrade/migrations"
)

func fetchRuntimeMessages(
Expand Down Expand Up @@ -171,6 +173,23 @@ func (app *Application) processRuntimeMessages(
func (app *Application) doBeforeSchedule(ctx *tmapi.Context, msg any) (any, error) {
epoch := msg.(beacon.EpochTime)

ok, err := features.IsFeatureVersion(ctx, migrations.Version242)
if err != nil {
return nil, err
}
if ok {
ctx.Logger().Debug("finalizing rounds before scheduling",
"epoch", epoch,
)

if err := app.tryFinalizeRounds(ctx); err != nil {
return nil, err
}
if err := app.processRoundTimeouts(ctx); err != nil {
return nil, err
}
}

ctx.Logger().Debug("processing liveness statistics before scheduling",
"epoch", epoch,
)
Expand Down
14 changes: 7 additions & 7 deletions go/consensus/cometbft/apps/scheduler/debug_force.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ type debugForceElectState struct {
elected map[signature.PublicKey]bool
}

func (app *Application) debugForceElect(
func debugForceElect(
ctx *api.Context,
schedulerParameters *scheduler.ConsensusParameters,
rt *registry.Runtime,
kind scheduler.CommitteeKind,
role scheduler.Role,
nodeList []*node.Node,
nodes []*node.Node,
wantedNodes int,
) (bool, []*scheduler.CommitteeNode, *debugForceElectState) {
elected := make([]*scheduler.CommitteeNode, 0, wantedNodes)
Expand Down Expand Up @@ -61,12 +61,12 @@ forceLoop:
}

// Ensure the node is currently registered and eligible.
for _, v := range nodeList {
for _, n := range nodes {
ctx.Logger().Debug("checking to see if this is the force elected node",
"iter_id", v.ID,
"iter_id", n.ID,
"node", nodeID,
)
if v.ID.Equal(nodeID) {
if n.ID.Equal(nodeID) {
// And force it into the committee.
elected = append(elected, &scheduler.CommitteeNode{
Role: role,
Expand All @@ -86,7 +86,7 @@ forceLoop:
ctx.Logger().Error("available nodes can't fulfill forced committee members",
"kind", kind,
"runtime_id", rt.ID,
"nr_nodes", len(nodeList),
"nr_nodes", len(nodes),
"mandatory_nodes", len(toForce),
)
return false, nil, nil
Expand All @@ -95,7 +95,7 @@ forceLoop:
return true, elected, state
}

func (app *Application) debugForceRoles(
func debugForceRoles(
ctx *api.Context,
state *debugForceElectState,
elected []*scheduler.CommitteeNode,
Expand Down
Loading
Loading