-
Notifications
You must be signed in to change notification settings - Fork 179
/
qc_voter.go
136 lines (116 loc) · 3.89 KB
/
qc_voter.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
package epochs
import (
"context"
"fmt"
"time"
"github.com/rs/zerolog"
"github.com/onflow/flow-go/consensus/hotstuff"
hotmodel "github.com/onflow/flow-go/consensus/hotstuff/model"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
clusterstate "github.com/onflow/flow-go/state/cluster"
"github.com/onflow/flow-go/state/protocol"
)
// RootQCVoter is responsible for generating and submitting votes for the
// root quorum certificate of the upcoming epoch for this node's cluster.
type RootQCVoter struct {
log zerolog.Logger
me module.Local
signer hotstuff.Signer
state protocol.State
client module.QCContractClient // client to the QC aggregator smart contract
wait time.Duration // how long to sleep in between vote attempts
}
// NewRootQCVoter returns a new root QC voter, configured for a particular epoch.
func NewRootQCVoter(
log zerolog.Logger,
me module.Local,
signer hotstuff.Signer,
state protocol.State,
client module.QCContractClient,
) *RootQCVoter {
voter := &RootQCVoter{
log: log.With().Str("module", "root_qc_voter").Logger(),
me: me,
signer: signer,
state: state,
client: client,
wait: time.Second * 10,
}
return voter
}
// Vote handles the full procedure of generating a vote, submitting it to the
// epoch smart contract, and verifying submission. Returns an error only if
// there is a critical error that would make it impossible for the vote to be
// submitted. Otherwise, exits when the vote has been successfully submitted.
//
// It is safe to run multiple times within a single setup phase.
func (voter *RootQCVoter) Vote(ctx context.Context, epoch protocol.Epoch) error {
counter, err := epoch.Counter()
if err != nil {
return fmt.Errorf("could not get epoch counter: %w", err)
}
clusters, err := epoch.Clustering()
if err != nil {
return fmt.Errorf("could not get clustering: %w", err)
}
cluster, clusterIndex, ok := clusters.ByNodeID(voter.me.NodeID())
if !ok {
return fmt.Errorf("could not find self in clustering")
}
log := voter.log.With().
Uint64("epoch", counter).
Uint("cluster_index", clusterIndex).
Logger()
log.Info().Msg("preparing to generate vote for cluster root qc")
// create the canonical root block for our cluster
root := clusterstate.CanonicalRootBlock(counter, cluster)
// create a signable hotstuff model
signable := hotmodel.GenesisBlockFromFlow(root.Header)
vote, err := voter.signer.CreateVote(signable)
if err != nil {
return fmt.Errorf("could not create vote for cluster root qc: %w", err)
}
attempts := 0
for {
attempts++
log := log.With().Int("attempt", attempts).Logger()
// for all attempts after the first, wait before re-trying
if attempts > 1 {
time.Sleep(voter.wait)
}
// check that our context is still valid
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled: %w", ctx.Err())
default:
}
// check that we're still in the setup phase, if we're not we can't
// submit a vote anyway and must exit this process
phase, err := voter.state.Final().Phase()
if err != nil {
log.Error().Err(err).Msg("could not get current phase")
} else if phase != flow.EpochPhaseSetup {
return fmt.Errorf("could not submit vote - no longer in setup phase")
}
// check whether we've already voted, if we have we can exit early
voted, err := voter.client.Voted(ctx)
if err != nil {
log.Error().Err(err).Msg("could not check vote status")
continue
} else if voted {
log.Info().Msg("already voted - exiting QC vote process...")
return nil
}
// submit the vote - this call will block until the transaction has
// either succeeded or we are able to retry
log.Info().Msg("submitting vote...")
err = voter.client.SubmitVote(ctx, vote)
if err != nil {
log.Error().Err(err).Msg("could not submit vote - retrying...")
continue
}
log.Info().Msg("successfully submitted vote - exiting QC vote process...")
return nil
}
}