Skip to content

Commit 8234ccd

Browse files
committed
Addressing PR review comments
1 parent dff1a8a commit 8234ccd

7 files changed

Lines changed: 120 additions & 119 deletions

File tree

icm-contracts/tests/flows/services/merkle_updater.go

Lines changed: 79 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ func MerkleUpdater(
6363

6464
ethClient := ethereumNetwork.EthClient
6565
_, ethFundedKey := ethereumNetwork.GetFundedAccountInfo()
66-
chainID := ethereumNetwork.ChainID
6766
fundedAddress, fundedKey := avalancheNetwork.GetFundedAccountInfo()
68-
_ = chainID
6967

7068
// =========================================================================
7169
// Setup: Fetch P-chain validators and compute the genesis Merkle root
@@ -144,7 +142,7 @@ func MerkleUpdater(
144142

145143
initialCommitment, err := contract.GetValidatorSetCommitment(callOpts, l1BlockchainID)
146144
Expect(err).Should(BeNil())
147-
Expect(initialCommitment.TotalWeight).Should(Equal(uint64(0)),
145+
Expect(initialCommitment.TotalWeight).Should(BeZero(),
148146
"L1 commitment should start empty")
149147

150148
log.Info("Contract state verified: P-chain initialized, L1 not yet registered")
@@ -193,37 +191,22 @@ func MerkleUpdater(
193191
// Wait for the relayer to register the initial L1 validator set.
194192
// totalWeight == 0 always triggers an immediate registration.
195193
// =========================================================================
196-
pollCtx, pollCancel := context.WithTimeout(ctx, 120*time.Second)
197-
defer pollCancel()
198-
199-
ticker := time.NewTicker(2 * time.Second)
200-
defer ticker.Stop()
201-
202-
var firstTotalWeight uint64
203-
var firstPChainHeight uint64
204-
205-
for done := false; !done; {
206-
select {
207-
case <-pollCtx.Done():
208-
Expect(pollCtx.Err()).ShouldNot(HaveOccurred(),
209-
"Timed out waiting for relayer to register L1 validator set")
210-
case <-ticker.C:
211-
cmt, err := contract.GetValidatorSetCommitment(callOpts, l1BlockchainID)
212-
if err != nil {
213-
log.Warn("Failed to query on-chain commitment", zap.Error(err))
214-
continue
215-
}
194+
firstCmt := pollForCommitmentUpdate(
195+
ctx, log, contract, callOpts, l1BlockchainID, 120*time.Second,
196+
"Timed out waiting for relayer to register L1 validator set",
197+
func(cmt merkleregistry.ValidatorSetMerkleCommitment) bool {
216198
if cmt.TotalWeight == 0 {
217199
log.Debug("Validator set not yet registered, waiting...")
218-
continue
200+
return false
219201
}
220-
202+
return true
203+
},
204+
func(cmt merkleregistry.ValidatorSetMerkleCommitment) {
221205
log.Info("Initial validator set registered",
222206
zap.Uint64("totalWeight", cmt.TotalWeight),
223207
zap.Uint64("pChainHeight", cmt.PChainHeight),
224208
zap.String("root", hex.EncodeToString(cmt.Root[:])),
225209
)
226-
227210
Expect(cmt.PChainHeight).Should(BeNumerically(">", 0),
228211
"P-chain height should be positive")
229212
Expect(cmt.PChainTimestamp).Should(BeNumerically(">", 0),
@@ -246,17 +229,13 @@ func MerkleUpdater(
246229
"Merkle root should match the P-chain validator set at the recorded height")
247230
Expect(cmt.TotalWeight).Should(Equal(expectedWeight),
248231
"Total weight should match the sum of validator weights")
249-
250-
firstTotalWeight = cmt.TotalWeight
251-
firstPChainHeight = cmt.PChainHeight
252-
done = true
253-
}
254-
}
232+
},
233+
)
255234

256235
firstRegistrationTime := time.Now()
257236
log.Info("Initial registration complete",
258-
zap.Uint64("firstTotalWeight", firstTotalWeight),
259-
zap.Uint64("firstPChainHeight", firstPChainHeight),
237+
zap.Uint64("firstTotalWeight", firstCmt.TotalWeight),
238+
zap.Uint64("firstPChainHeight", firstCmt.PChainHeight),
260239
)
261240

262241
// =========================================================================
@@ -314,43 +293,26 @@ func MerkleUpdater(
314293

315294
// 90s timeout — well under the staleness cap. If the update arrives in this window
316295
// it was triggered by the root change, not by staleness.
317-
rootChangeCtx, rootChangeCancel := context.WithTimeout(ctx, 90*time.Second)
318-
defer rootChangeCancel()
319-
320-
rootChangeTicker := time.NewTicker(2 * time.Second)
321-
defer rootChangeTicker.Stop()
322-
323-
var secondPChainHeight uint64
324-
var secondTotalWeight uint64
325-
var secondRoot [32]byte
326-
327-
for {
328-
select {
329-
case <-rootChangeCtx.Done():
330-
Expect(false).Should(BeTrue(),
331-
"Phase 1: Timed out waiting for root-change-triggered update")
332-
case <-rootChangeTicker.C:
333-
cmt, err := contract.GetValidatorSetCommitment(callOpts, l1BlockchainID)
334-
if err != nil {
335-
log.Warn("Failed to query on-chain commitment", zap.Error(err))
336-
continue
337-
}
338-
339-
if cmt.PChainHeight <= firstPChainHeight {
296+
secondCmt := pollForCommitmentUpdate(
297+
ctx, log, contract, callOpts, l1BlockchainID, 90*time.Second,
298+
"Phase 1: Timed out waiting for root-change-triggered update",
299+
func(cmt merkleregistry.ValidatorSetMerkleCommitment) bool {
300+
if cmt.PChainHeight <= firstCmt.PChainHeight {
340301
log.Debug("Phase 1: Waiting for root-change update...",
341302
zap.Uint64("currentPChainHeight", cmt.PChainHeight),
342303
)
343-
continue
304+
return false
344305
}
345-
306+
return true
307+
},
308+
func(cmt merkleregistry.ValidatorSetMerkleCommitment) {
346309
log.Info("Phase 1: Root-change update detected!",
347310
zap.Uint64("totalWeight", cmt.TotalWeight),
348311
zap.Uint64("pChainHeight", cmt.PChainHeight),
349312
zap.String("root", hex.EncodeToString(cmt.Root[:])),
350313
)
351-
352-
Expect(cmt.PChainHeight).Should(BeNumerically(">", firstPChainHeight))
353-
Expect(cmt.TotalWeight).Should(BeNumerically(">", firstTotalWeight),
314+
Expect(cmt.PChainHeight).Should(BeNumerically(">", firstCmt.PChainHeight))
315+
Expect(cmt.TotalWeight).Should(BeNumerically(">", firstCmt.TotalWeight),
354316
"Phase 1: New commitment should include the added validator's weight")
355317
Expect(cmt.Root).ShouldNot(Equal([32]byte{}))
356318

@@ -361,18 +323,13 @@ func MerkleUpdater(
361323
expectedRoot := validatorupdater.BuildMerkleRoot(expectedValidators)
362324
Expect(cmt.Root).Should(Equal(expectedRoot),
363325
"Phase 1: Merkle root should match the updated P-chain validator set")
364-
365-
secondPChainHeight = cmt.PChainHeight
366-
secondTotalWeight = cmt.TotalWeight
367-
secondRoot = cmt.Root
368-
}
369-
break
370-
}
326+
},
327+
)
371328

372329
secondUpdateTime := time.Now()
373330
log.Info("Phase 1 PASSED: Root-change-triggered update confirmed",
374-
zap.Uint64("secondTotalWeight", secondTotalWeight),
375-
zap.Uint64("secondPChainHeight", secondPChainHeight),
331+
zap.Uint64("secondTotalWeight", secondCmt.TotalWeight),
332+
zap.Uint64("secondPChainHeight", secondCmt.PChainHeight),
376333
)
377334

378335
// =========================================================================
@@ -400,49 +357,73 @@ func MerkleUpdater(
400357
zap.Duration("remainingWait", remainingWait),
401358
)
402359

403-
stalenessCtx, stalenessCancel := context.WithTimeout(ctx, remainingWait)
404-
defer stalenessCancel()
405-
406-
stalenessTicker := time.NewTicker(2 * time.Second)
407-
defer stalenessTicker.Stop()
408-
409-
for {
410-
select {
411-
case <-stalenessCtx.Done():
412-
Expect(false).Should(BeTrue(),
413-
"Phase 2: Timed out waiting for staleness-forced update")
414-
case <-stalenessTicker.C:
415-
cmt, err := contract.GetValidatorSetCommitment(callOpts, l1BlockchainID)
416-
if err != nil {
417-
log.Warn("Failed to query on-chain commitment", zap.Error(err))
418-
continue
419-
}
420-
421-
if cmt.PChainHeight <= secondPChainHeight {
360+
pollForCommitmentUpdate(
361+
ctx, log, contract, callOpts, l1BlockchainID, remainingWait,
362+
"Phase 2: Timed out waiting for staleness-forced update",
363+
func(cmt merkleregistry.ValidatorSetMerkleCommitment) bool {
364+
if cmt.PChainHeight <= secondCmt.PChainHeight {
422365
log.Debug("Phase 2: Still waiting for staleness update...",
423366
zap.Duration("elapsed", time.Since(secondUpdateTime)),
424367
)
425-
continue
368+
return false
426369
}
427-
370+
return true
371+
},
372+
func(cmt merkleregistry.ValidatorSetMerkleCommitment) {
428373
log.Info("Phase 2: Staleness-forced update detected!",
429374
zap.Uint64("totalWeight", cmt.TotalWeight),
430375
zap.Uint64("pChainHeight", cmt.PChainHeight),
431376
zap.String("root", hex.EncodeToString(cmt.Root[:])),
432377
)
433-
434-
Expect(cmt.PChainHeight).Should(BeNumerically(">", secondPChainHeight))
435-
Expect(cmt.TotalWeight).Should(Equal(secondTotalWeight),
378+
Expect(cmt.PChainHeight).Should(BeNumerically(">", secondCmt.PChainHeight))
379+
Expect(cmt.TotalWeight).Should(Equal(secondCmt.TotalWeight),
436380
"Phase 2: Total weight should be unchanged (no new validators)")
437-
Expect(cmt.Root).Should(Equal(secondRoot),
381+
Expect(cmt.Root).Should(Equal(secondCmt.Root),
438382
"Phase 2: Merkle root should be unchanged (no new validators)")
439383

440384
log.Info("Phase 2 PASSED: Staleness-forced update confirmed")
385+
},
386+
)
387+
388+
log.Info("MerkleUpdater e2e test PASSED")
389+
}
390+
391+
func pollForCommitmentUpdate(
392+
ctx context.Context,
393+
log logging.Logger,
394+
contract *merkleregistry.MerkleValidatorSetRegistry,
395+
callOpts *bind.CallOpts,
396+
l1BlockchainID ids.ID,
397+
timeout time.Duration,
398+
timeoutMsg string,
399+
isUpdated func(cmt merkleregistry.ValidatorSetMerkleCommitment) bool,
400+
validate func(cmt merkleregistry.ValidatorSetMerkleCommitment),
401+
) merkleregistry.ValidatorSetMerkleCommitment {
402+
pollCtx, pollCancel := context.WithTimeout(ctx, timeout)
403+
defer pollCancel()
404+
405+
ticker := time.NewTicker(2 * time.Second)
406+
defer ticker.Stop()
407+
408+
for {
409+
select {
410+
case <-pollCtx.Done():
411+
Expect(false).Should(BeTrue(), timeoutMsg)
412+
case <-ticker.C:
413+
cmt, err := contract.GetValidatorSetCommitment(callOpts, l1BlockchainID)
414+
if err != nil {
415+
log.Warn("Failed to query on-chain commitment", zap.Error(err))
416+
continue
417+
}
418+
if !isUpdated(cmt) {
419+
continue
420+
}
421+
validate(cmt)
422+
return cmt
441423
}
442424
break
443425
}
444-
445-
log.Info("MerkleUpdater e2e test PASSED")
426+
panic("unreachable")
446427
}
447428

448429
func createMerkleUpdaterRelayerConfig(

peers/clients/validator_client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type CanonicalValidatorState interface {
3232
GetAllValidatorSets(ctx context.Context, pchainHeight uint64) (map[ids.ID]validators.WarpSet, error)
3333
GetProposedValidators(ctx context.Context, subnetID ids.ID) (validators.WarpSet, error)
3434
GetCurrentValidators(ctx context.Context, subnetID ids.ID) ([]platformvm.ClientPermissionlessValidator, error)
35+
GetValidatorsAt(ctx context.Context, subnetID ids.ID, pchainHeight uint64) (map[ids.NodeID]*validators.GetValidatorOutput, error)
3536
}
3637

3738
// CanonicalValidatorClient wraps [platformvm.Client] and implements [CanonicalValidatorState]
@@ -105,6 +106,10 @@ func (v *CanonicalValidatorClient) GetProposedValidators(
105106
return validators.FlattenValidatorSet(res)
106107
}
107108

109+
func (v *CanonicalValidatorClient) GetValidatorsAt(ctx context.Context, subnetID ids.ID, pchainHeight uint64) (map[ids.NodeID]*validators.GetValidatorOutput, error) {
110+
return v.client.GetValidatorsAt(ctx, subnetID, pchainapi.Height(pchainHeight), v.options...)
111+
}
112+
108113
// Gets the validator set of the given subnet at the given P-chain block height.
109114
// Uses [platform.getValidatorsAt] with supplied height
110115
func (v *CanonicalValidatorClient) GetAllValidatorSets(

relayer/main/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,9 @@ func startMerkleSetUpdater(
849849

850850
pollInterval := time.Duration(extDest.PollIntervalSeconds) * time.Second
851851
maxUpdateInterval := time.Duration(extDest.MaxUpdateIntervalSeconds) * time.Second
852-
852+
if pollInterval == 0 {
853+
pollInterval = validatorupdater.DefaultPollInterval
854+
}
853855
updater := validatorupdater.NewMerkleSetUpdater(
854856
logger,
855857
pChainClient,

relayer/validatorupdater/diff_set_updater.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func NewDiffSetUpdater(
7373
shardSize = defaultShardSize
7474
}
7575
if pollInterval == 0 {
76-
pollInterval = defaultPollInterval
76+
pollInterval = DefaultPollInterval
7777
}
7878
return &DiffSetUpdater{
7979
logger: logger,

relayer/validatorupdater/merkle_validator_updater.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ func NewMerkleSetUpdater(
6161
pollInterval time.Duration,
6262
maxUpdateInterval time.Duration,
6363
) *MerkleSetUpdater {
64-
if pollInterval == 0 {
65-
pollInterval = defaultPollInterval
66-
}
6764
return &MerkleSetUpdater{
6865
logger: logger,
6966
pChainClient: pChainClient,
@@ -152,7 +149,7 @@ func (s *MerkleSetUpdater) checkAndUpdate(ctx context.Context) error {
152149
if err := s.performUpdate(ctx, s.localPChainHeight, validatorSetUpdate, false); err != nil {
153150
s.logger.Warn("Merkle root update failed, re-syncing from contract on next tick", zap.Error(err))
154151
s.initialized = false
155-
return err
152+
return nil
156153
}
157154

158155
s.localValidatorSet = newValidators
@@ -289,22 +286,28 @@ func (s *MerkleSetUpdater) performUpdate(
289286
return fmt.Errorf("failed to sign message: %w", err)
290287
}
291288

292-
return s.sendUpdate(ctx, signedMsg, isFirstRegistration)
289+
return s.sendUpdate(ctx, signedMsg, onChainPChainHeight, isFirstRegistration)
293290
}
294291

295292
func (s *MerkleSetUpdater) sendUpdate(
296293
ctx context.Context,
297294
signedMsg *avalancheWarp.Message,
295+
onChainPChainHeight uint64,
298296
isFirstRegistration bool,
299297
) error {
300298
var attestationValidators []*Validator
301299
if isFirstRegistration {
302300
// First registration is verified against the P-chain Merkle root stored in the
303301
// contract constructor. Fetch the primary network validators used to build that
304302
// root so the attestation proof is computed against the correct ordered set.
305-
pChainWarpSet, err := s.pChainClient.GetProposedValidators(ctx, ids.Empty)
303+
allValidatorSets, err := s.pChainClient.GetAllValidatorSets(ctx, onChainPChainHeight)
306304
if err != nil {
307-
return fmt.Errorf("failed to get P-chain validators for attestation: %w", err)
305+
return fmt.Errorf("failed to get P-chain validator sets at height %d for attestation: %w",
306+
onChainPChainHeight, err)
307+
}
308+
pChainWarpSet, ok := allValidatorSets[ids.Empty]
309+
if !ok {
310+
return fmt.Errorf("primary network not found in validator sets at height %d", onChainPChainHeight)
308311
}
309312
attestationValidators = make([]*Validator, len(pChainWarpSet.Validators))
310313
for i, vdr := range pChainWarpSet.Validators {
@@ -364,22 +367,19 @@ func (s *MerkleSetUpdater) fetchSortedValidators(
364367
ctx context.Context,
365368
pChainHeight uint64,
366369
) ([]*Validator, error) {
367-
allValidatorSets, err := s.pChainClient.GetAllValidatorSets(ctx, pChainHeight)
370+
subnetValidatorSet, err := s.pChainClient.GetValidatorsAt(ctx, s.subnetID, pChainHeight)
368371
if err != nil {
369372
return nil, fmt.Errorf("failed to get validator sets: %w", err)
370373
}
371374

372-
vdrSet, ok := allValidatorSets[s.subnetID]
373-
if !ok {
374-
return nil, fmt.Errorf("subnet %s not found in validator sets at height %d", s.subnetID, pChainHeight)
375-
}
376-
377-
validators := make([]*Validator, len(vdrSet.Validators))
378-
for i, vdr := range vdrSet.Validators {
379-
validators[i] = &Validator{
375+
validators := make([]*Validator, len(subnetValidatorSet))
376+
ix := 0
377+
for _, vdr := range subnetValidatorSet {
378+
validators[ix] = &Validator{
380379
UncompressedPublicKeyBytes: [96]byte(vdr.PublicKey.Serialize()),
381380
Weight: vdr.Weight,
382381
}
382+
ix++
383383
}
384384
sort.Slice(validators, func(i, j int) bool {
385385
return string(validators[i].UncompressedPublicKeyBytes[:]) < string(validators[j].UncompressedPublicKeyBytes[:])

0 commit comments

Comments
 (0)