forked from canopy-network/canopy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresult.go
More file actions
320 lines (305 loc) · 12.3 KB
/
Copy pathresult.go
File metadata and controls
320 lines (305 loc) · 12.3 KB
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
package controller
import (
"bytes"
"slices"
"time"
"github.com/canopy-network/canopy/bft"
"github.com/canopy-network/canopy/fsm"
"github.com/canopy-network/canopy/lib"
)
/* This file implements the 'Certificate Result' logic which ensures the */
// NewCertificateResults() creates a structure to hold the results of the certificate produced by a quorum in consensus
func (c *Controller) NewCertificateResults(
fsm *fsm.StateMachine, block *lib.Block, blockResult *lib.BlockResult,
evidence *bft.ByzantineEvidence, rcBuildHeight uint64) (results *lib.CertificateResult) {
defer lib.TimeTrack(c.log, time.Now(), 500*time.Millisecond)
// calculate reward recipients, creating a 'certificate results' object reference in the process
results = c.CalculateRewardRecipients(fsm, block.BlockHeader.ProposerAddress, rcBuildHeight)
// handle swaps
c.HandleSwaps(fsm, blockResult, results, rcBuildHeight)
// handle dex
c.HandleDex(fsm, results, rcBuildHeight)
// set slash recipients
c.CalculateSlashRecipients(results, evidence)
// set checkpoint
c.CalculateCheckpoint(blockResult, results)
// handle retired status
c.HandleRetired(fsm, results)
// exit
return
}
// SendCertificateResultsTx() originates and auto-sends a CertificateResultsTx after successfully leading a Consensus height
func (c *Controller) SendCertificateResultsTx(qc *lib.QuorumCertificate) {
// get the root chain id from the state
rootChainId := c.LoadRootChainId(c.ChainHeight())
// if the chain is its own root, don't send a transaction
if c.Config.ChainId == rootChainId {
// exit
return
}
// save the block data from the quorum certificate
blk := qc.Block
// set the block data back in the certificate object after this function completes
defer func() { qc.Block = blk }()
// omit the block when sending the transaction as it's not relevant to the root chain
qc.Block = nil
// create a new certificate results transaction
tx, err := fsm.NewCertificateResultsTx(c.PrivateKey, qc, rootChainId, c.Config.NetworkID, 0, c.RootChainHeight(), "")
// if an error occurred during the tx creation
if err != nil {
// log the error
c.log.Errorf("Creating auto-certificate-results-txn failed with err: %s", err.Error())
// exit
return
}
// handle the transaction on the root-chain
hash, err := c.RCManager.Transaction(rootChainId, tx)
// if an error occurred during the tx submission
if err != nil {
// log the error
c.log.Errorf("Submitting auto-certificate-results-txn failed with err: %s", err.Error())
// exit
return
}
// log the submission of the transaction
c.log.Infof("Successfully submitted the certificate-results-txn with hash %s", *hash)
}
// CalculateRewardRecipients() calculates the block reward recipients of the proposal
func (c *Controller) CalculateRewardRecipients(fsm *fsm.StateMachine, proposerAddress []byte, rootChainHeight uint64) (results *lib.CertificateResult) {
// set block reward recipients
results = &lib.CertificateResult{
RewardRecipients: &lib.RewardRecipients{},
SlashRecipients: new(lib.SlashRecipients),
}
// get the root chain id from the governance params in the state machine
rootChainId, err := fsm.GetRootChainId()
// if an error occurred
if err != nil {
// log the error
c.log.Warnf("An error occurred getting the root chain id from state: %s", err.Error())
// exit with a non-nil result
return results
}
// create variables to hold potential 'reward recipients' (delegate, nested-validator, nested-delegate)
var delegate, nValidator, nDelegate *lib.LotteryWinner
// start the proposer with a 100% allocation
proposer := &lib.LotteryWinner{Winner: proposerAddress, Cut: 100}
// get the delegate and their cut from the state machine
delegate, err = c.GetRootChainLotteryWinner(fsm, rootChainHeight)
// if an error occurred
if err != nil {
// log the error and continue
c.log.Warnf("An error occurred choosing a root chain delegate lottery winner: %s", err.Error())
}
// calculate if this chain is its own root chain
isOwnRoot := rootChainId == c.Config.ChainId
// add the delegate as a reward recipient, subtracting share away from the proposer
c.AddRewardRecipient(proposer, delegate, results, isOwnRoot, rootChainId)
// if this chain isn't its own root chain, add the nested participants to the 'reward recipients
if !isOwnRoot {
// get the nested-validator lottery winner for the 'self chain' (if self is not root)
nValidator, err = fsm.LotteryWinner(c.Config.ChainId, true)
// if an error occurred
if err != nil {
// log the error and continue
c.log.Warnf("An error occurred choosing a nested-validator lottery winner: %s", err.Error())
}
// add the nested validator as a reward recipient, subtracting share away from the proposer
c.AddRewardRecipient(proposer, nValidator, results, isOwnRoot, rootChainId)
// get the nested-delegate lottery winner for the 'self chain' (if self is not root)
nDelegate, err = fsm.LotteryWinner(c.Config.ChainId)
// if an error occurred
if err != nil {
// log the error and continue
c.log.Warnf("An error occurred choosing a nested-delegate lottery winner: %s", err.Error())
}
// add the nested delegate as a reward recipient, subtracting share away from the proposer
c.AddRewardRecipient(proposer, nDelegate, results, isOwnRoot, rootChainId)
}
// finally add the proposer at the end after ensuring their proper percent
c.AddPaymentPercent(proposer, results, isOwnRoot, rootChainId)
// exit
return
}
// AddRewardRecipient() adds a reward recipient to the list of reward recipients in the certificate result
func (c *Controller) AddRewardRecipient(proposer, toAdd *lib.LotteryWinner, results *lib.CertificateResult, isOwnRoot bool, rootChainId uint64) {
// skip any nil recipient
if toAdd == nil || len(toAdd.Winner) == 0 {
// exit
return
}
// ensure the new recipient's cut doesn't underflow the proposers cut
if proposer.Cut < toAdd.Cut {
c.log.Warnf("Not enough proposer cut for winner")
// exit
return
}
// calculate new proposer cut
proposer.Cut -= toAdd.Cut
// add the payment percent for the recipient
c.AddPaymentPercent(toAdd, results, isOwnRoot, rootChainId)
}
// AddPaymentPercent() adds a payment percent to the certificate result for both the root chain and nested chain ids (if different)
func (c *Controller) AddPaymentPercent(toAdd *lib.LotteryWinner, results *lib.CertificateResult, isOwnRoot bool, rootChainId uint64) {
// add the payment percent for the participant for the 'root chain id'
c.addPaymentPercent(toAdd, results, rootChainId)
// if this chain is not its own root (nested chain)
if !isOwnRoot {
// add the payment percent for the participant for the 'nested chain id'
c.addPaymentPercent(toAdd, results, c.Config.ChainId)
}
}
// addPaymentPercent() is a helper function to add a payment percent to the certificate result
func (c *Controller) addPaymentPercent(toAdd *lib.LotteryWinner, results *lib.CertificateResult, chainId uint64) {
// don't add 0% cuts
if toAdd.Cut == 0 {
return
}
// check if the winner's address is already in the reward recipients list for the root chain
// if found, update their reward percentage - else add them as a new recipient
if !slices.ContainsFunc(results.RewardRecipients.PaymentPercents, func(pp *lib.PaymentPercents) (has bool) {
// if the address and chain id matches
if bytes.Equal(pp.Address, toAdd.Winner) && pp.ChainId == chainId {
// mark as found
has = true
// increase their reward percentage by 'cut'
pp.Percent += toAdd.Cut
}
return
}) {
// if the winner is not found in the list, add them as a new recipient
results.RewardRecipients.PaymentPercents = append(results.RewardRecipients.PaymentPercents,
&lib.PaymentPercents{Address: toAdd.Winner, Percent: toAdd.Cut, ChainId: chainId})
}
}
// HandleSwaps() handles the 'buy' side of the sell orders
func (c *Controller) HandleSwaps(fsm *fsm.StateMachine, blockResult *lib.BlockResult, results *lib.CertificateResult, rootChainHeight uint64) {
var orders *lib.OrderBook
// load the root chain id
rootChainId, err := fsm.GetRootChainId()
if err != nil {
c.log.Error(err.Error())
// exit without handling
return
}
// check if own root
ownRoot, err := fsm.LoadIsOwnRoot()
if err != nil {
c.log.Error(err.Error())
// exit without handling
return
}
// execute a remote call to get the root chains order book to enact the 'buyer side'
if !ownRoot {
// get orders from the root-chain
orders, err = c.LoadRootChainOrderBook(rootChainId, rootChainHeight)
} else {
orders, err = fsm.GetOrderBook(c.Config.ChainId)
}
// if an error occurred while loading the orders
if err != nil {
c.log.Error(err.Error())
// exit without handling
return
}
// process the root chain order book against the state
lockOrders, closeOrders, resetOrders := fsm.ProcessRootChainOrderBook(orders, blockResult)
// add the orders to the certificate result - truncating the 'lock orders' for defensive spam protection
results.Orders = &lib.Orders{
LockOrders: lib.TruncateSlice(lockOrders, 1000),
ResetOrders: resetOrders,
CloseOrders: closeOrders,
}
}
// CalculateSlashRecipients() calculates the addresses who receive slashes on the root-chain
func (c *Controller) CalculateSlashRecipients(results *lib.CertificateResult, be *bft.ByzantineEvidence) {
// define an error variable to be able to populate the Double signers directly
var err lib.ErrorI
// use the bft object to fill in the Byzantine Evidence
results.SlashRecipients.DoubleSigners, err = c.Consensus.ProcessDSE(be.DSE.Evidence...)
// if an error occurred
if err != nil {
// log the warning
c.log.Warn(err.Error())
// exit
return
}
// if any slash recipients added
if numSlashRecipients := len(results.SlashRecipients.DoubleSigners); numSlashRecipients != 0 {
// log the addition
c.log.Infof("Added %d slash recipients due to byzantine evidence", numSlashRecipients)
}
}
// define how often the chain checkpoints with its root
const CheckpointFrequency = 100
// CalculateCheckpoint() calculates the checkpoint for the checkpoint as a service functionality
func (c *Controller) CalculateCheckpoint(blockResult *lib.BlockResult, results *lib.CertificateResult) {
// each checkpoint frequency
if blockResult.BlockHeader.Height%CheckpointFrequency == 0 {
// log the addition to the certificate results
c.log.Info("Checkpoint set in certificate results")
// update the checkpoint in the certificate results
results.Checkpoint = &lib.Checkpoint{
Height: blockResult.BlockHeader.Height,
BlockHash: blockResult.BlockHeader.Hash,
}
}
}
// HandleRetired() checks if the committee is retiring and sets in the results accordingly
func (c *Controller) HandleRetired(fsm *fsm.StateMachine, results *lib.CertificateResult) {
// get the governance params from the 'nested chain' FSM
cons, err := fsm.GetParamsCons()
// if an error occurred
if err != nil {
// log the error
c.log.Error(err.Error())
// exit
return
}
// set the 'retired' field based on the retired consensus param not being 0
results.Retired = cons.Retired != 0
}
// HandleDex() populates the certificate with 'dex' information
func (c *Controller) HandleDex(sm *fsm.StateMachine, results *lib.CertificateResult, rcBuildHeight uint64) {
rcId, err := sm.GetRootChainId()
if err != nil {
c.log.Error(err.Error())
return
}
// set the dex batch based on the 'locked batch' for the root chain id
batch, err := sm.GetDexBatch(rcId, true)
if err != nil {
c.log.Error(err.Error())
return
}
// ensure liquidity pool is enabled
balance, err := sm.GetPoolBalance(rcId + fsm.LiquidityPoolAddend)
if err != nil {
c.log.Error(err.Error())
return
}
if balance == 0 {
return
}
isTriggerBlock := false
// check if 'locked' dex batch is non empty
if !batch.IsEmpty() {
// calculate the 'blocks since' the lock
blksSince := sm.Height() - batch.LockedHeight
if isTriggerBlock = blksSince%lib.TriggerModuloBlocks == 0; isTriggerBlock {
results.DexBatch = batch.Copy()
}
}
// if nested, populate the root dex batch structure with the
if rcId != c.Config.ChainId {
// determine if we should activate liveness fallback
livenessFallback := isTriggerBlock && !batch.IsEmpty() && (sm.Height()-batch.LockedHeight) >= lib.LivenessFallbackBlocks
// set the root chain dex batch
if results.RootDexBatch, err = c.RCManager.GetDexBatch(rcId, rcBuildHeight, c.Config.ChainId, livenessFallback); err != nil {
c.log.Error(err.Error())
return
}
// set the liveness fallback flag
results.RootDexBatch.LivenessFallback = livenessFallback
}
}