-
Notifications
You must be signed in to change notification settings - Fork 107
/
keymanager_rotation_failure.go
258 lines (222 loc) · 7.8 KB
/
keymanager_rotation_failure.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package runtime
import (
"context"
"fmt"
beacon "github.com/oasisprotocol/oasis-core/go/beacon/api"
"github.com/oasisprotocol/oasis-core/go/common/crypto/signature"
"github.com/oasisprotocol/oasis-core/go/common/node"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/consensus/api/transaction"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/env"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/scenario"
"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"
)
// KeymanagerRotationFailure is a scenario where the first master secret proposal is rejected
// because not enough nodes have replicated the secret. The second proposal is accepted,
// ensuring that nodes can properly handle potential reverts.
//
// Scenario:
// - Start all key managers.
// - Verify that master secret generation works.
// - Stop the third key manager.
// - Verify that the next proposal is not accepted.
// - Repeat these steps N times.
var KeymanagerRotationFailure scenario.Scenario = newKmRotationFailureImpl()
type kmRotationFailureImpl struct {
Scenario
}
func newKmRotationFailureImpl() scenario.Scenario {
return &kmRotationFailureImpl{
Scenario: *NewScenario("keymanager-rotation-failure", nil),
}
}
func (sc *kmRotationFailureImpl) Clone() scenario.Scenario {
return &kmRotationFailureImpl{
Scenario: *sc.Scenario.Clone().(*Scenario),
}
}
func (sc *kmRotationFailureImpl) Fixture() (*oasis.NetworkFixture, error) {
f, err := sc.Scenario.Fixture()
if err != nil {
return nil, err
}
// Speed up the test.
f.Network.Beacon.VRFParameters = &beacon.VRFParameters{
Interval: 10,
ProofSubmissionDelay: 2,
}
// We don't need compute workers.
f.ComputeWorkers = []oasis.ComputeWorkerFixture{}
// This requires multiple keymanagers.
f.Keymanagers = []oasis.KeymanagerFixture{
{Runtime: 0, Entity: 1, Policy: 0},
{Runtime: 0, Entity: 1, Policy: 0},
{Runtime: 0, Entity: 1, Policy: 0},
}
// Enable master secret rotation.
// The rotation interval should be set to at least 2 so that key manager nodes can be shut down
// after they have accepted the last generation but before a new master secret is proposed.
f.KeymanagerPolicies[0].MasterSecretRotationInterval = 2
return f, nil
}
func (sc *kmRotationFailureImpl) Run(ctx context.Context, _ *env.Env) error {
var generation uint64
// Start the first key manager.
if err := sc.Net.Start(); err != nil {
return err
}
for i := 0; i < 2; i++ {
// Verify that master secret generation works with all key managers.
for j := 0; j < 2; j++ {
if err := sc.verifyMasterSecret(ctx, generation, 3); err != nil {
return err
}
generation++
}
// Give key managers enough time to apply the last proposal and register with the latests
// checksum. This process can take several blocks.
if _, err := sc.WaitBlocks(ctx, 5); err != nil {
return err
}
// Stop two key managers, leaving only 33% of the committee members to be active.
if err := sc.StopKeymanagers([]int{1, 2}); err != nil {
return err
}
// Extend registrations to ensure that stopped key managers remain on the committee.
if err := sc.extendKeymanagerRegistrations(ctx, []int{1, 2}); err != nil {
return err
}
// Verify that the next few master secret proposals are rejected.
// Note that the proposals will be rejected only until the registrations
// of the stopped key managers expire.
if err := sc.verifyMasterSecretRejections(ctx, 3); err != nil {
return err
}
// Verify that master secret generation works with one key manager
// after registrations expire.
for j := 0; j < 2; j++ {
if err := sc.verifyMasterSecret(ctx, generation, 1); err != nil {
return err
}
generation++
}
// Start stopped key managers.
//
// Note: Starting both key managers while they are still registered
// may lead to a scenario where they both attempt to replicate ephemeral
// secrets from each other. Since they are both still uninitialized,
// replication requests will fail and retries will block initialization
// for 15 seconds.
if err := sc.StartKeymanagers([]int{1, 2}); err != nil {
return err
}
// Key managers that have been started should join the committee in
// the following epoch, unless consensus sync takes a lot of time.
// Due to uncertainty about the committee size, we skip validation
// of the next generation.
if _, err := sc.WaitMasterSecret(ctx, generation); err != nil {
return err
}
generation++
}
return nil
}
func (sc *kmRotationFailureImpl) verifyMasterSecret(ctx context.Context, generation uint64, committeeSize int) error {
status, err := sc.WaitMasterSecret(ctx, generation)
if err != nil {
return fmt.Errorf("master secret was not generated: %w", err)
}
if status.Generation != generation {
return fmt.Errorf("master secret generation number is not correct: expected %d, got %d", generation, status.Generation)
}
if size := len(status.Nodes); size != committeeSize {
return fmt.Errorf("key manager committee's size is not correct: expected %d, got %d", committeeSize, size)
}
return nil
}
func (sc *kmRotationFailureImpl) extendKeymanagerRegistrations(ctx context.Context, idxs []int) error {
sc.Logger.Info("extending registrations of the key managers", "ids", fmt.Sprintf("%+v", idxs))
// Compute the maximum expiration epoch.
epoch, err := sc.Net.ClientController().Beacon.GetEpoch(ctx, consensus.HeightLatest)
if err != nil {
return err
}
params, err := sc.Net.ClientController().Consensus.Registry().ConsensusParameters(ctx, consensus.HeightLatest)
if err != nil {
return err
}
expiration := uint64(epoch) + params.MaxNodeExpiration
for _, idx := range idxs {
km := sc.Net.Keymanagers()[idx]
// Update expiration.
nodeDesc, err := sc.Net.ClientController().Registry.GetNode(ctx, &api.IDQuery{
Height: consensus.HeightLatest,
ID: km.NodeID,
})
if err != nil {
return err
}
nodeDesc.Expiration = expiration
// Prepare, sign and submit the register node transaction.
identity, err := km.LoadIdentity()
if err != nil {
return err
}
nodeSigners := []signature.Signer{
identity.NodeSigner,
identity.P2PSigner,
identity.ConsensusSigner,
identity.VRFSigner,
identity.TLSSigner,
}
sigNode, err := node.MultiSignNode(nodeSigners, registry.RegisterNodeSignatureContext, nodeDesc)
if err != nil {
return err
}
nonce, err := sc.Net.Controller().Consensus.GetSignerNonce(ctx, &consensus.GetSignerNonceRequest{
AccountAddress: staking.NewAddress(identity.NodeSigner.Public()),
Height: consensus.HeightLatest,
})
if err != nil {
return err
}
tx := registry.NewRegisterNodeTx(nonce, &transaction.Fee{Gas: 50000}, sigNode)
sigTx, err := transaction.Sign(identity.NodeSigner, tx)
if err != nil {
return err
}
err = sc.Net.Controller().Consensus.SubmitTx(ctx, sigTx)
if err != nil {
return err
}
}
return nil
}
func (sc *kmRotationFailureImpl) verifyMasterSecretRejections(ctx context.Context, n int) error {
mstCh, mstSub, err := sc.Net.Controller().Keymanager.Secrets().WatchMasterSecrets(ctx)
if err != nil {
return err
}
defer mstSub.Close()
generations := make(map[uint64]struct{})
for j := 0; j < n; j++ {
select {
case secret := <-mstCh:
sc.Logger.Info("master secret proposed",
"generation", secret.Secret.Generation,
"epoch", secret.Secret.Epoch,
"num_ciphertexts", len(secret.Secret.Secret.Ciphertexts),
)
generations[secret.Secret.Generation] = struct{}{}
if len(generations) != 1 {
return fmt.Errorf("master secret proposal was not rejected")
}
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}