forked from ava-labs/avalanchego
-
Notifications
You must be signed in to change notification settings - Fork 4
/
windower.go
142 lines (122 loc) · 3.69 KB
/
windower.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
// Copyright (C) 2019-2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package proposer
import (
"context"
"time"
"github.com/MetalBlockchain/metalgo/ids"
"github.com/MetalBlockchain/metalgo/snow/validators"
"github.com/MetalBlockchain/metalgo/utils"
"github.com/MetalBlockchain/metalgo/utils/math"
"github.com/MetalBlockchain/metalgo/utils/sampler"
"github.com/MetalBlockchain/metalgo/utils/wrappers"
)
// Proposer list constants
const (
MaxWindows = 6
WindowDuration = 5 * time.Second
MaxDelay = MaxWindows * WindowDuration
)
var _ Windower = (*windower)(nil)
type Windower interface {
// Proposers returns the proposer list for building a block at [chainHeight]
// when the validator set is defined at [pChainHeight]. The list is returned
// in order. The minimum delay of a validator is the index they appear times
// [WindowDuration].
Proposers(
ctx context.Context,
chainHeight,
pChainHeight uint64,
) ([]ids.NodeID, error)
// Delay returns the amount of time that [validatorID] must wait before
// building a block at [chainHeight] when the validator set is defined at
// [pChainHeight].
Delay(
ctx context.Context,
chainHeight,
pChainHeight uint64,
validatorID ids.NodeID,
) (time.Duration, error)
}
// windower interfaces with P-Chain and it is responsible for calculating the
// delay for the block submission window of a given validator
type windower struct {
state validators.State
subnetID ids.ID
chainSource uint64
sampler sampler.WeightedWithoutReplacement
}
func New(state validators.State, subnetID, chainID ids.ID) Windower {
w := wrappers.Packer{Bytes: chainID[:]}
return &windower{
state: state,
subnetID: subnetID,
chainSource: w.UnpackLong(),
sampler: sampler.NewDeterministicWeightedWithoutReplacement(),
}
}
func (w *windower) Proposers(ctx context.Context, chainHeight, pChainHeight uint64) ([]ids.NodeID, error) {
// get the validator set by the p-chain height
validatorsMap, err := w.state.GetValidatorSet(ctx, pChainHeight, w.subnetID)
if err != nil {
return nil, err
}
// convert the map of validators to a slice
validators := make([]validatorData, 0, len(validatorsMap))
weight := uint64(0)
for k, v := range validatorsMap {
validators = append(validators, validatorData{
id: k,
weight: v.Weight,
})
newWeight, err := math.Add64(weight, v.Weight)
if err != nil {
return nil, err
}
weight = newWeight
}
// canonically sort validators
// Note: validators are sorted by ID, sorting by weight would not create a
// canonically sorted list
utils.Sort(validators)
// convert the slice of validators to a slice of weights
validatorWeights := make([]uint64, len(validators))
for i, v := range validators {
validatorWeights[i] = v.weight
}
if err := w.sampler.Initialize(validatorWeights); err != nil {
return nil, err
}
numToSample := MaxWindows
if weight < uint64(numToSample) {
numToSample = int(weight)
}
seed := chainHeight ^ w.chainSource
w.sampler.Seed(int64(seed))
indices, err := w.sampler.Sample(numToSample)
if err != nil {
return nil, err
}
nodeIDs := make([]ids.NodeID, numToSample)
for i, index := range indices {
nodeIDs[i] = validators[index].id
}
return nodeIDs, nil
}
func (w *windower) Delay(ctx context.Context, chainHeight, pChainHeight uint64, validatorID ids.NodeID) (time.Duration, error) {
if validatorID == ids.EmptyNodeID {
return MaxDelay, nil
}
proposers, err := w.Proposers(ctx, chainHeight, pChainHeight)
if err != nil {
return 0, err
}
delay := time.Duration(0)
for _, nodeID := range proposers {
if nodeID == validatorID {
return delay, nil
}
delay += WindowDuration
}
return delay, nil
}