Skip to content

Commit

Permalink
Refresh node descriptors mid-epoch
Browse files Browse the repository at this point in the history
Previously node descriptors were only refreshed on an epoch transition which
meant that any later updates were ignored. This caused stale RAKs to stay in
effect when runtime restarts happened.

Enabling mid-epoch refresh also makes having more ephemeral keys easier.
  • Loading branch information
kostko committed Jan 23, 2020
1 parent 6f42638 commit 7f3f147
Show file tree
Hide file tree
Showing 15 changed files with 506 additions and 315 deletions.
8 changes: 8 additions & 0 deletions .changelog/1794.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Refresh node descriptors mid-epoch.

Previously node descriptors were only refreshed on an epoch transition which meant that any later
updates were ignored until the next epoch. This caused stale RAKs to stay in effect when runtime
restarts happened, causing attestation verification to fail.

Enabling mid-epoch refresh makes nodes stay up to date with committee member node descriptor
updates.
16 changes: 12 additions & 4 deletions go/common/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,25 @@ func (n *Node) IsExpired(epoch uint64) bool {
return n.Expiration < epoch
}

// AddOrUpdateRuntime searches for an existing supported runtime descriptor in Runtimes and returns
// it. In case a runtime descriptor for the given runtime doesn't exist yet, a new one is created
// appended to the list of supported runtimes and returned.
func (n *Node) AddOrUpdateRuntime(id common.Namespace) *Runtime {
// GetRuntime searches for an existing supported runtime descriptor in Runtimes and returns it.
func (n *Node) GetRuntime(id common.Namespace) *Runtime {
for _, rt := range n.Runtimes {
if !rt.ID.Equal(&id) {
continue
}

return rt
}
return nil
}

// AddOrUpdateRuntime searches for an existing supported runtime descriptor in Runtimes and returns
// it. In case a runtime descriptor for the given runtime doesn't exist yet, a new one is created
// appended to the list of supported runtimes and returned.
func (n *Node) AddOrUpdateRuntime(id common.Namespace) *Runtime {
if rt := n.GetRuntime(id); rt != nil {
return rt
}

rt := &Runtime{ID: id}
n.Runtimes = append(n.Runtimes, rt)
Expand Down
37 changes: 0 additions & 37 deletions go/consensus/tendermint/apps/roothash/roothash.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import (
"github.com/oasislabs/oasis-core/go/common"
"github.com/oasislabs/oasis-core/go/common/cbor"
"github.com/oasislabs/oasis-core/go/common/crypto/hash"
"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/common/logging"
"github.com/oasislabs/oasis-core/go/common/node"
"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
tmapi "github.com/oasislabs/oasis-core/go/consensus/tendermint/api"
Expand Down Expand Up @@ -246,40 +244,12 @@ func (app *rootHashApplication) prepareNewCommittees(
empty = true
}
for _, executorCommittee := range executorCommittees {
executorNodeInfo := make(map[signature.PublicKey]commitment.NodeInfo)
for idx, n := range executorCommittee.Members {
var nodeRuntime *node.Runtime
node, err1 := regState.Node(n.PublicKey)
if err1 != nil {
return hash.Hash{}, nil, nil, false, errors.Wrap(err1, "checkCommittees: failed to query node")
}
for _, r := range node.Runtimes {
if !r.ID.Equal(&rtID) {
continue
}
nodeRuntime = r
break
}
if nodeRuntime == nil {
// We currently prevent this case throughout the rest of the system.
// Still, it's prudent to check.
ctx.Logger().Warn("checkCommittees: committee member not registered with this runtime",
"node", n.PublicKey,
)
continue
}
executorNodeInfo[n.PublicKey] = commitment.NodeInfo{
CommitteeNode: idx,
Runtime: nodeRuntime,
}
}
executorCommitteeID := executorCommittee.EncodedMembersHash()
committeeIDParts = append(committeeIDParts, executorCommitteeID[:])

executorPool.Committees[executorCommitteeID] = &commitment.Pool{
Runtime: rtState.Runtime,
Committee: executorCommittee,
NodeInfo: executorNodeInfo,
}
}

Expand All @@ -299,16 +269,9 @@ func (app *rootHashApplication) prepareNewCommittees(
)
empty = true
} else {
mergeNodeInfo := make(map[signature.PublicKey]commitment.NodeInfo)
for idx, n := range mergeCommittee.Members {
mergeNodeInfo[n.PublicKey] = commitment.NodeInfo{
CommitteeNode: idx,
}
}
mergePool = &commitment.Pool{
Runtime: rtState.Runtime,
Committee: mergeCommittee,
NodeInfo: mergeNodeInfo,
}
mergeCommitteeID := mergeCommittee.EncodedMembersHash()
committeeIDParts = append(committeeIDParts, mergeCommitteeID[:])
Expand Down
16 changes: 12 additions & 4 deletions go/consensus/tendermint/apps/roothash/state/round.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,26 @@ func (r *Round) GetNextTimeout() (timeout time.Time) {
return
}

func (r *Round) AddExecutorCommitment(commitment *commitment.ExecutorCommitment, sv commitment.SignatureVerifier) (*commitment.Pool, error) {
func (r *Round) AddExecutorCommitment(
commitment *commitment.ExecutorCommitment,
sv commitment.SignatureVerifier,
nl commitment.NodeLookup,
) (*commitment.Pool, error) {
if r.Finalized {
return nil, errors.New("tendermint/roothash: round is already finalized, can't commit")
}
return r.ExecutorPool.AddExecutorCommitment(r.CurrentBlock, sv, commitment)
return r.ExecutorPool.AddExecutorCommitment(r.CurrentBlock, sv, nl, commitment)
}

func (r *Round) AddMergeCommitment(commitment *commitment.MergeCommitment, sv commitment.SignatureVerifier) error {
func (r *Round) AddMergeCommitment(
commitment *commitment.MergeCommitment,
sv commitment.SignatureVerifier,
nl commitment.NodeLookup,
) error {
if r.Finalized {
return errors.New("tendermint/roothash: round is already finalized, can't commit")
}
return r.MergePool.AddMergeCommitment(r.CurrentBlock, sv, commitment, r.ExecutorPool)
return r.MergePool.AddMergeCommitment(r.CurrentBlock, sv, nl, commitment, r.ExecutorPool)
}

func (r *Round) Transition(blk *block.Block) {
Expand Down
22 changes: 13 additions & 9 deletions go/consensus/tendermint/apps/roothash/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/oasislabs/oasis-core/go/common"
"github.com/oasislabs/oasis-core/go/common/crypto/signature"
"github.com/oasislabs/oasis-core/go/consensus/tendermint/abci"
registryState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/registry/state"
roothashState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/roothash/state"
schedulerState "github.com/oasislabs/oasis-core/go/consensus/tendermint/apps/scheduler/state"
roothash "github.com/oasislabs/oasis-core/go/roothash/api"
Expand Down Expand Up @@ -59,17 +60,17 @@ func (app *rootHashApplication) getRuntimeState(
ctx *abci.Context,
state *roothashState.MutableState,
id common.Namespace,
) (*roothashState.RuntimeState, commitment.SignatureVerifier, error) {
) (*roothashState.RuntimeState, commitment.SignatureVerifier, commitment.NodeLookup, error) {
// Fetch current runtime state.
rtState, err := state.RuntimeState(id)
if err != nil {
return nil, nil, fmt.Errorf("roothash: failed to fetch runtime state: %w", err)
return nil, nil, nil, fmt.Errorf("roothash: failed to fetch runtime state: %w", err)
}
if rtState.Suspended {
return nil, nil, roothash.ErrRuntimeSuspended
return nil, nil, nil, roothash.ErrRuntimeSuspended
}
if rtState.Round == nil {
return nil, nil, roothash.ErrNoRound
return nil, nil, nil, roothash.ErrNoRound
}

// Create signature verifier.
Expand All @@ -78,6 +79,9 @@ func (app *rootHashApplication) getRuntimeState(
scheduler: schedulerState.NewMutableState(ctx.State()),
}

// Create node lookup.
nl := registryState.NewMutableState(ctx.State())

// If the round was finalized, transition.
if rtState.Round.CurrentBlock.Header.Round != rtState.CurrentBlock.Header.Round {
ctx.Logger().Debug("round was finalized, transitioning round",
Expand All @@ -87,7 +91,7 @@ func (app *rootHashApplication) getRuntimeState(
rtState.Round.Transition(rtState.CurrentBlock)
}

return rtState, sv, nil
return rtState, sv, nl, nil
}

func (app *rootHashApplication) executorCommit(
Expand All @@ -111,7 +115,7 @@ func (app *rootHashApplication) executorCommit(
return err
}

rtState, sv, err := app.getRuntimeState(ctx, state, cc.ID)
rtState, sv, nl, err := app.getRuntimeState(ctx, state, cc.ID)
if err != nil {
return err
}
Expand All @@ -120,7 +124,7 @@ func (app *rootHashApplication) executorCommit(
pools := make(map[*commitment.Pool]bool)
for _, commit := range cc.Commits {
var pool *commitment.Pool
if pool, err = rtState.Round.AddExecutorCommitment(&commit, sv); err != nil {
if pool, err = rtState.Round.AddExecutorCommitment(&commit, sv, nl); err != nil {
ctx.Logger().Error("failed to add compute commitment to round",
"err", err,
"round", rtState.CurrentBlock.Header.Round,
Expand Down Expand Up @@ -160,15 +164,15 @@ func (app *rootHashApplication) mergeCommit(
return err
}

rtState, sv, err := app.getRuntimeState(ctx, state, mc.ID)
rtState, sv, nl, err := app.getRuntimeState(ctx, state, mc.ID)
if err != nil {
return err
}
defer state.SetRuntimeState(rtState)

// Add commitments.
for _, commit := range mc.Commits {
if err = rtState.Round.AddMergeCommitment(&commit, sv); err != nil {
if err = rtState.Round.AddMergeCommitment(&commit, sv, nl); err != nil {
ctx.Logger().Error("failed to add merge commitment to round",
"err", err,
"round", rtState.CurrentBlock.Header.Round,
Expand Down
Loading

0 comments on commit 7f3f147

Please sign in to comment.