@@ -12,6 +12,18 @@ import (
1212 "go.uber.org/zap"
1313)
1414
15+ type missingBlock struct {
16+ digest Digest
17+ seq uint64
18+ }
19+
20+ func missingBlockFromBlock (block Block ) missingBlock {
21+ return missingBlock {
22+ digest : block .BlockHeader ().Digest ,
23+ seq : block .BlockHeader ().Seq ,
24+ }
25+ }
26+
1527type finalizedQuorumRound struct {
1628 block Block
1729 finalization * Finalization
@@ -33,7 +45,7 @@ type ReplicationState struct {
3345 // it means a prior notarization for that block exists but is missing.
3446 // Since we may not know which round that dependency belongs to,
3547 // digestTimeouts ensures we re-request the missing digest until it arrives.
36- digestTimeouts * TimeoutHandler [Digest ]
48+ digestTimeouts * TimeoutHandler [missingBlock ]
3749
3850 // emptyRoundTimeouts handles timeouts for fetching missing empty round notarizations.
3951 // When replication encounters a notarized block that depends on an empty round we haven't received,
@@ -42,6 +54,9 @@ type ReplicationState struct {
4254
4355 roundRequestor * requestor
4456 finalizationRequestor * requestor
57+
58+ sender sender
59+ epochLock * sync.Mutex
4560}
4661
4762func NewReplicationState (logger Logger , comm Communication , myNodeID NodeID , maxRoundWindow uint64 , enabled bool , start time.Time , lock * sync.Mutex ) * ReplicationState {
@@ -64,6 +79,10 @@ func NewReplicationState(logger Logger, comm Communication, myNodeID NodeID, max
6479 // round replication
6580 rounds : make (map [uint64 ]* QuorumRound ),
6681 roundRequestor : newRequestor (logger , start , lock , maxRoundWindow , comm , false ),
82+
83+ // sender for missing dependencies
84+ sender : comm ,
85+ epochLock : lock ,
6786 }
6887
6988 r .digestTimeouts = NewTimeoutHandler (logger , "digest" , start , DefaultReplicationRequestTimeout , r .requestDigests )
@@ -110,15 +129,15 @@ func (r *ReplicationState) storeSequence(block Block, finalization *Finalization
110129 }
111130
112131 r .finalizationRequestor .removeTask (finalization .Finalization .Seq )
113- r .digestTimeouts .RemoveTask (block . BlockHeader (). Digest )
132+ r .digestTimeouts .RemoveTask (missingBlockFromBlock ( block ) )
114133}
115134
116135// storeRound adds or updates a quorum round in the replication state.
117136// If the round already exists, it merges any missing notarizations or empty notarizations
118137// from the provided quorum round. Otherwise, it stores the new round as is.
119138func (r * ReplicationState ) storeRound (qr * QuorumRound ) {
120139 if qr .Block != nil {
121- r .digestTimeouts .RemoveTask (qr .Block . BlockHeader (). Digest )
140+ r .digestTimeouts .RemoveTask (missingBlockFromBlock ( qr .Block ) )
122141 }
123142
124143 existing , exists := r .rounds [qr .GetRound ()]
@@ -222,7 +241,7 @@ func (r *ReplicationState) ResendFinalizationRequest(seq uint64, signers []NodeI
222241// TODO: in a future PR, these requests will be sent as specific digest requests.
223242func (r * ReplicationState ) CreateDependencyTasks (parent * Digest , parentSeq uint64 , emptyRounds []uint64 ) {
224243 if parent != nil {
225- r .digestTimeouts .AddTask (* parent )
244+ r .digestTimeouts .AddTask (missingBlock { digest : * parent , seq : parentSeq } )
226245 }
227246
228247 if len (emptyRounds ) > 0 {
@@ -296,9 +315,41 @@ func (r *ReplicationState) GetBlockWithSeq(seq uint64) Block {
296315 return nil
297316}
298317
299- func (r * ReplicationState ) requestDigests (digests []Digest ) {
300- // TODO: In a future PR, I will add a message that requests a specific digest.
301- r .logger .Debug ("Not implemented yet" , zap .Stringers ("Digests" , digests ))
318+ func (r * ReplicationState ) requestDigests (missingBlocks []missingBlock ) {
319+ // grab the lock since this is called in the timeout handler goroutine
320+ r .epochLock .Lock ()
321+ defer r .epochLock .Unlock ()
322+
323+ signedQuorum := r .roundRequestor .getHighestObserved ()
324+ if signedQuorum == nil {
325+ signedQuorum = r .finalizationRequestor .getHighestObserved ()
326+ }
327+ if signedQuorum == nil {
328+ r .logger .Warn ("Replication State cannot request missing block digests, no known nodes to request from" )
329+ return
330+ }
331+
332+ randInt , err := rand .Int (rand .Reader , big .NewInt (int64 (len (signedQuorum .signers ))))
333+ if err != nil {
334+ r .logger .Info ("Replication State failed to generate random starting index" , zap .Error (err ))
335+ return
336+ }
337+ startingIndex := int (randInt .Int64 ())
338+
339+ for i , mb := range missingBlocks {
340+ // grab the node to send it to
341+ index := (i + startingIndex ) % len (signedQuorum .signers )
342+ node := signedQuorum .signers [index ]
343+
344+ r .logger .Debug ("Replication State requesting missing block digest" , zap .Uint64 ("seq" , mb .seq ), zap .Stringer ("digest" , & mb .digest ))
345+ blockRequest := & BlockDigestRequest {
346+ Seq : mb .seq ,
347+ Digest : mb .digest ,
348+ }
349+ r .sender .Send (& Message {
350+ BlockDigestRequest : blockRequest ,
351+ }, node )
352+ }
302353}
303354
304355func (r * ReplicationState ) requestEmptyRounds (emptyRounds []uint64 ) {
0 commit comments