Skip to content

Commit 4c44920

Browse files
authored
Merge pull request #255 from canopy-network/eviction-optimization
Eviction/LoadRootChainInfo optimization
2 parents 961c0d0 + 72ca467 commit 4c44920

15 files changed

Lines changed: 219 additions & 139 deletions

File tree

cmd/rpc/query.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,14 @@ func (s *Server) RootChainInfo(w http.ResponseWriter, r *http.Request, _ httprou
127127
if ok := unmarshal(w, r, req); !ok {
128128
return
129129
}
130+
// if height is 0; set to the latest height
131+
if req.Height == 0 {
132+
req.Height = s.controller.FSM.Height()
133+
}
134+
// retrieve the saved last validator set if available
135+
lastVS := s.controller.LastValidatorSet[req.Height][req.ID]
130136
// load the root chain info directly
131-
got, err := s.controller.FSM.LoadRootChainInfo(req.ID, req.Height)
137+
got, err := s.controller.FSM.LoadRootChainInfo(req.ID, req.Height, lastVS)
132138
if err != nil {
133139
write(w, err, http.StatusBadRequest)
134140
return

cmd/rpc/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
const (
3232
colon = ":"
3333

34-
SoftwareVersion = "beta-0.1.11"
34+
SoftwareVersion = "beta-0.1.12"
3535
ContentType = "Content-MessageType"
3636
ApplicationJSON = "application/json; charset=utf-8"
3737

controller/block.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func (c *Controller) ValidateProposal(rcBuildHeight uint64, qc *lib.QuorumCertif
203203
return
204204
}
205205
// play the block against the state machine to generate a block result
206-
blockResult, err = c.ApplyAndValidateBlock(block, false)
206+
blockResult, err = c.ApplyAndValidateBlock(block, c.LastValidatorSet[c.ChainHeight()][c.Config.ChainId], false)
207207
if err != nil {
208208
// exit with error
209209
return
@@ -244,7 +244,7 @@ func (c *Controller) CommitCertificate(qc *lib.QuorumCertificate, block *lib.Blo
244244
// reset the FSM to ensure stale proposal validations don't come into play
245245
c.FSM.Reset()
246246
// apply the block against the state machine
247-
blockResult, err = c.ApplyAndValidateBlock(block, true)
247+
blockResult, err = c.ApplyAndValidateBlock(block, c.LastValidatorSet[c.ChainHeight()][c.Config.ChainId], true)
248248
if err != nil {
249249
// exit with error
250250
return
@@ -284,6 +284,8 @@ func (c *Controller) CommitCertificate(qc *lib.QuorumCertificate, block *lib.Blo
284284
c.log.Infof("Committed block %s at H:%d 🔒", lib.BytesToTruncatedString(qc.BlockHash), block.BlockHeader.Height)
285285
// set up the finite state machine for the next height
286286
c.FSM, err = fsm.New(c.Config, storeI, c.Metrics, c.log)
287+
// set the reference to lastCertificate on the new FSM
288+
c.FSM.LastValidatorSet = c.LastValidatorSet
287289
if err != nil {
288290
// exit with error
289291
return err
@@ -300,10 +302,15 @@ func (c *Controller) CommitCertificate(qc *lib.QuorumCertificate, block *lib.Blo
300302
c.Mempool.FSM.Reset()
301303
// update telemetry (using proper defer to ensure time.Since is evaluated at defer execution)
302304
defer c.UpdateTelemetry(qc, block, time.Since(start))
303-
// publish the root chain info to the nested chain subscribers
304-
for _, id := range c.RCManager.ChainIds() {
305+
// publish root chain information to all nested chain subscribers.
306+
// Currently using hardcoded chain IDs (1, 2) instead of dynamically fetching IDs
307+
// from RCManager since only these two chains exist. This will be updated to use
308+
// dynamic chain discovery when additional chains are added.
309+
// TODO: Optimize rcManager publishing for subchains (it should publish to themselves too by default)
310+
// for _, id := range c.RCManager.ChainIds() {
311+
for _, id := range []uint64{1, 2} {
305312
// get the root chain info
306-
info, e := c.FSM.LoadRootChainInfo(id, 0)
313+
info, e := c.FSM.LoadRootChainInfo(id, 0, c.LastValidatorSet[c.ChainHeight()][id])
307314
if e != nil {
308315
// don't log 'no-validators' error as this is possible
309316
if e.Error() != lib.ErrNoValidators().Error() {
@@ -313,9 +320,17 @@ func (c *Controller) CommitCertificate(qc *lib.QuorumCertificate, block *lib.Blo
313320
}
314321
// set the timestamp
315322
info.Timestamp = ts
323+
// save current validator set for the next height
324+
valSet, _ := c.FSM.GetCommitteeMembers(id)
325+
if _, found := c.LastValidatorSet[c.ChainHeight()+1]; !found {
326+
c.LastValidatorSet[c.ChainHeight()+1] = make(map[uint64]*lib.ValidatorSet)
327+
}
328+
c.LastValidatorSet[c.ChainHeight()+1][id] = &valSet
316329
// publish root chain information
317330
go c.RCManager.Publish(id, info)
318331
}
332+
// remove older validator set heights
333+
delete(c.LastValidatorSet, c.ChainHeight()-2)
319334
// exit
320335
return
321336
}
@@ -339,7 +354,7 @@ func (c *Controller) CommitCertificateParallel(qc *lib.QuorumCertificate, block
339354
// reset the FSM to ensure stale proposal validations don't come into play
340355
c.FSM.Reset()
341356
// apply the block against the state machine
342-
blockResult, err = c.ApplyAndValidateBlock(block, true)
357+
blockResult, err = c.ApplyAndValidateBlock(block, c.LastValidatorSet[c.ChainHeight()][c.Config.ChainId], true)
343358
if err != nil {
344359
// exit with error
345360
return
@@ -395,8 +410,18 @@ func (c *Controller) CommitCertificateParallel(qc *lib.QuorumCertificate, block
395410
}
396411
// publish the root chain info to the nested chain subscribers
397412
for _, id := range c.RCManager.ChainIds() {
413+
// get latest validator set
414+
valSet, err := c.FSM.GetCommitteeMembers(id)
415+
if err != nil {
416+
return err
417+
}
418+
// TODO handle err
419+
if _, found := c.LastValidatorSet[c.ChainHeight()+1]; !found {
420+
c.LastValidatorSet[c.ChainHeight()+1] = make(map[uint64]*lib.ValidatorSet)
421+
}
422+
c.LastValidatorSet[c.ChainHeight()+1][id] = &valSet
398423
// get the root chain info
399-
info, e := c.FSM.LoadRootChainInfo(id, 0)
424+
info, e := c.FSM.LoadRootChainInfo(id, 0, c.LastValidatorSet[c.ChainHeight()][id])
400425
if e != nil {
401426
// don't log 'no-validators' error as this is possible
402427
if e.Error() != lib.ErrNoValidators().Error() {
@@ -445,7 +470,7 @@ func (c *Controller) CommitCertificateParallel(qc *lib.QuorumCertificate, block
445470
// INTERNAL HELPERS BELOW
446471

447472
// ApplyAndValidateBlock() plays the block against the state machine which returns a result that is compared against the candidate block header
448-
func (c *Controller) ApplyAndValidateBlock(block *lib.Block, commit bool) (b *lib.BlockResult, err lib.ErrorI) {
473+
func (c *Controller) ApplyAndValidateBlock(block *lib.Block, lastValidatorSet *lib.ValidatorSet, commit bool) (b *lib.BlockResult, err lib.ErrorI) {
449474
// define convenience variables for the block header, hash, and height
450475
candidate, candidateHash, candidateHeight := block.BlockHeader, lib.BytesToString(block.BlockHeader.Hash), block.BlockHeader.Height
451476
// check the last qc in the candidate and set it in the ephemeral indexer to prepare for block application
@@ -456,7 +481,7 @@ func (c *Controller) ApplyAndValidateBlock(block *lib.Block, commit bool) (b *li
456481
// log the start of 'apply block'
457482
c.log.Debugf("Applying block %s for height %d", candidateHash[:20], candidateHeight)
458483
// apply the block against the state machine
459-
compare, txResults, _, failed, err := c.FSM.ApplyBlock(context.Background(), block, false)
484+
compare, txResults, _, failed, err := c.FSM.ApplyBlock(context.Background(), block, lastValidatorSet, false)
460485
if err != nil {
461486
// exit with error
462487
return

controller/controller.go

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ var _ bft.Controller = new(Controller)
1818

1919
// Controller acts as the 'manager' of the modules of the application
2020
type Controller struct {
21-
Address []byte // self address
22-
PublicKey []byte // self public key
23-
PrivateKey crypto.PrivateKeyI // self private key
24-
Config lib.Config // node configuration
25-
Metrics *lib.Metrics // telemetry
21+
Address []byte // self address
22+
PublicKey []byte // self public key
23+
PrivateKey crypto.PrivateKeyI // self private key
24+
Config lib.Config // node configuration
25+
Metrics *lib.Metrics // telemetry
26+
LastValidatorSet map[uint64]map[uint64]*lib.ValidatorSet // cache [height][chainID] -> set
2627

2728
FSM *fsm.StateMachine // the core protocol component responsible for maintaining and updating the state of the blockchain
2829
Mempool *Mempool // the in memory list of pending transactions
@@ -54,23 +55,26 @@ func New(fsm *fsm.StateMachine, c lib.Config, valKey crypto.PrivateKeyI, metrics
5455
}
5556
// create the controller
5657
controller = &Controller{
57-
Address: address.Bytes(),
58-
PublicKey: valKey.PublicKey().Bytes(),
59-
PrivateKey: valKey,
60-
Config: c,
61-
Metrics: metrics,
62-
FSM: fsm,
63-
Mempool: mempool,
64-
Consensus: nil,
65-
P2P: p2p.New(valKey, maxMembersPerCommittee, metrics, c, l),
66-
isSyncing: &atomic.Bool{},
67-
log: l,
68-
Mutex: &sync.Mutex{},
58+
Address: address.Bytes(),
59+
PublicKey: valKey.PublicKey().Bytes(),
60+
PrivateKey: valKey,
61+
Config: c,
62+
Metrics: metrics,
63+
FSM: fsm,
64+
Mempool: mempool,
65+
Consensus: nil,
66+
P2P: p2p.New(valKey, maxMembersPerCommittee, metrics, c, l),
67+
isSyncing: &atomic.Bool{},
68+
LastValidatorSet: make(map[uint64]map[uint64]*lib.ValidatorSet),
69+
log: l,
70+
Mutex: &sync.Mutex{},
6971
}
7072
// initialize the consensus in the controller, passing a reference to itself
7173
controller.Consensus, err = bft.New(c, valKey, fsm.Height(), fsm.Height()-1, controller, c.RunVDF, metrics, l)
7274
// initialize the mempool controller
7375
mempool.controller = controller
76+
// add the last validator set reference to the FSM
77+
fsm.LastValidatorSet = controller.LastValidatorSet
7478
// if an error occurred initializing the bft module
7579
if err != nil {
7680
// exit with error
@@ -94,6 +98,21 @@ func (c *Controller) Start() {
9498
c.log.Warnf("Attempting to connect to the root-chain")
9599
// set a timer to go off once per second
96100
t := time.NewTicker(time.Second)
101+
// pre save the validator sets from previous and current heights
102+
lastValSet, err := c.FSM.LoadCommittee(c.Config.ChainId, c.ChainHeight()-1)
103+
if err != nil {
104+
c.log.Fatal(err.Error())
105+
}
106+
currValSet, err := c.FSM.LoadCommittee(c.Config.ChainId, c.ChainHeight())
107+
if err != nil {
108+
c.log.Fatal(err.Error())
109+
}
110+
c.LastValidatorSet[c.ChainHeight()] = map[uint64]*lib.ValidatorSet{
111+
c.Config.ChainId: &lastValSet,
112+
}
113+
c.LastValidatorSet[c.ChainHeight()+1] = map[uint64]*lib.ValidatorSet{
114+
c.Config.ChainId: &currValSet,
115+
}
97116
// once function completes, stop the timer
98117
defer t.Stop()
99118
// each time the timer fires

controller/tx.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package controller
33
import (
44
"context"
55
"fmt"
6-
"github.com/canopy-network/canopy/bft"
76
"math"
87
"sync"
98
"sync/atomic"
109
"time"
1110

11+
"github.com/canopy-network/canopy/bft"
12+
1213
"github.com/canopy-network/canopy/fsm"
1314
"github.com/canopy-network/canopy/lib"
1415
"github.com/canopy-network/canopy/lib/crypto"
@@ -226,7 +227,8 @@ func (m *Mempool) CheckMempool() {
226227
// set the cancel function
227228
m.stop = stop
228229
// apply the block against the state machine and populate the resulting merkle `roots` in the block header
229-
block.BlockHeader, blockResult.Transactions, oversized, failed, err = m.FSM.ApplyBlock(ctx, block, true)
230+
lastVs := m.controller.LastValidatorSet[m.FSM.Height()][m.controller.Config.ChainId]
231+
block.BlockHeader, blockResult.Transactions, oversized, failed, err = m.FSM.ApplyBlock(ctx, block, lastVs, true)
230232
if err != nil {
231233
m.log.Warnf("Check Mempool error: %s", err.Error())
232234
return

fsm/automatic.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
/* This file handles 'automatic' (non-transaction-induced) state changes that occur ath the beginning and ending of a block */
88

99
// BeginBlock() is code that is executed at the start of `applying` the block
10-
func (s *StateMachine) BeginBlock() lib.ErrorI {
10+
func (s *StateMachine) BeginBlock(lastValidatorSet *lib.ValidatorSet) lib.ErrorI {
1111
// prevent attempting to load the certificate for height 0
1212
if s.Height() <= 1 {
1313
return nil
@@ -39,11 +39,7 @@ func (s *StateMachine) BeginBlock() lib.ErrorI {
3939
}
4040
// if is root-chain: load the committee from state as the certificate result
4141
// will match the evidence and there's no Transaction to HandleMessageCertificateResults
42-
committee, err := s.LoadCommittee(s.Config.ChainId, s.Height()-1)
43-
if err != nil {
44-
return err
45-
}
46-
return s.HandleCertificateResults(lastCertificate, &committee)
42+
return s.HandleCertificateResults(lastCertificate, lastValidatorSet)
4743
}
4844

4945
// EndBlock() is code that is executed at the end of `applying` the block

fsm/automatic_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package fsm
22

33
import (
44
"fmt"
5+
"math"
6+
"testing"
7+
58
"github.com/canopy-network/canopy/lib"
69
"github.com/canopy-network/canopy/lib/crypto"
710
"github.com/stretchr/testify/require"
8-
"math"
9-
"testing"
1011
)
1112

1213
func TestBeginBlock(t *testing.T) {
@@ -122,8 +123,10 @@ func TestBeginBlock(t *testing.T) {
122123
if test.isGenesis {
123124
sm.height = 1
124125
}
126+
// get last validator set for begin block
125127
// ensure expected error on function call
126-
require.Equal(t, test.error, sm.BeginBlock())
128+
valSet, _ := sm.LoadCommittee(lib.CanopyChainId, sm.Height()-1)
129+
require.Equal(t, test.error, sm.BeginBlock(&valSet))
127130
if test.error != nil {
128131
return
129132
}

fsm/message.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package fsm
22

33
import (
44
"bytes"
5+
56
"github.com/canopy-network/canopy/lib"
67
"github.com/canopy-network/canopy/lib/crypto"
78
)
@@ -318,14 +319,22 @@ func (s *StateMachine) HandleMessageCertificateResults(msg *MessageCertificateRe
318319
if poolBalance == 0 {
319320
return ErrNonSubsidizedCommittee()
320321
}
321-
// get committee for the QC
322-
committee, err := s.LoadCommittee(chainId, msg.Qc.Header.RootHeight)
323-
if err != nil {
324-
return err
322+
// get committee for the QC from the cache
323+
var committee *lib.ValidatorSet
324+
if s.LastValidatorSet != nil {
325+
committee = s.LastValidatorSet[msg.Qc.Header.RootHeight+1][chainId]
326+
}
327+
if committee == nil {
328+
// otherwise, retrieve it from the store
329+
valSet, err := s.LoadCommittee(chainId, msg.Qc.Header.RootHeight)
330+
if err != nil {
331+
return err
332+
}
333+
committee = &valSet
325334
}
326335
// ensure it's a valid QC
327336
// max block size is 0 here because there should not be a block attached to this QC
328-
isPartialQC, err := msg.Qc.Check(committee, 0, &lib.View{NetworkId: uint64(s.NetworkID), ChainId: chainId}, false)
337+
isPartialQC, err := msg.Qc.Check(*committee, 0, &lib.View{NetworkId: uint64(s.NetworkID), ChainId: chainId}, false)
329338
if err != nil {
330339
return err
331340
}
@@ -334,7 +343,7 @@ func (s *StateMachine) HandleMessageCertificateResults(msg *MessageCertificateRe
334343
return lib.ErrNoMaj23()
335344
}
336345
// handle the certificate results
337-
return s.HandleCertificateResults(msg.Qc, &committee)
346+
return s.HandleCertificateResults(msg.Qc, committee)
338347
}
339348

340349
// HandleMessageSubsidy() is the proper handler for a `Subsidy` message

0 commit comments

Comments
 (0)