11package verifier
22
33import (
4+ "context"
45 "errors"
56 "fmt"
7+ "sync"
68
79 "github.com/rs/zerolog"
810 "github.com/rs/zerolog/log"
@@ -25,7 +27,7 @@ import (
2527// It assumes the latest sealed block has been executed, and the chunk data packs have not been
2628// pruned.
2729// Note, it returns nil if certain block is not executed, in this case warning will be logged
28- func VerifyLastKHeight (k uint64 , chainID flow.ChainID , protocolDataDir string , chunkDataPackDir string ) (err error ) {
30+ func VerifyLastKHeight (k uint64 , chainID flow.ChainID , protocolDataDir string , chunkDataPackDir string , nWorker uint ) (err error ) {
2931 closer , storages , chunkDataPacks , state , verifier , err := initStorages (chainID , protocolDataDir , chunkDataPackDir )
3032 if err != nil {
3133 return fmt .Errorf ("could not init storages: %w" , err )
@@ -62,12 +64,9 @@ func VerifyLastKHeight(k uint64, chainID flow.ChainID, protocolDataDir string, c
6264
6365 log .Info ().Msgf ("verifying blocks from %d to %d" , from , to )
6466
65- for height := from ; height <= to ; height ++ {
66- log .Info ().Uint64 ("height" , height ).Msg ("verifying height" )
67- err := verifyHeight (height , storages .Headers , chunkDataPacks , storages .Results , state , verifier )
68- if err != nil {
69- return fmt .Errorf ("could not verify height %d: %w" , height , err )
70- }
67+ err = verifyConcurrently (from , to , nWorker , storages .Headers , chunkDataPacks , storages .Results , state , verifier , verifyHeight )
68+ if err != nil {
69+ return err
7170 }
7271
7372 return nil
@@ -79,6 +78,7 @@ func VerifyRange(
7978 from , to uint64 ,
8079 chainID flow.ChainID ,
8180 protocolDataDir string , chunkDataPackDir string ,
81+ nWorker uint ,
8282) (err error ) {
8383 closer , storages , chunkDataPacks , state , verifier , err := initStorages (chainID , protocolDataDir , chunkDataPackDir )
8484 if err != nil {
@@ -99,12 +99,94 @@ func VerifyRange(
9999 return fmt .Errorf ("cannot verify blocks before the root block, from: %d, root: %d" , from , root )
100100 }
101101
102- for height := from ; height <= to ; height ++ {
103- log .Info ().Uint64 ("height" , height ).Msg ("verifying height" )
104- err := verifyHeight (height , storages .Headers , chunkDataPacks , storages .Results , state , verifier )
105- if err != nil {
106- return fmt .Errorf ("could not verify height %d: %w" , height , err )
102+ err = verifyConcurrently (from , to , nWorker , storages .Headers , chunkDataPacks , storages .Results , state , verifier , verifyHeight )
103+ if err != nil {
104+ return err
105+ }
106+
107+ return nil
108+ }
109+
110+ func verifyConcurrently (
111+ from , to uint64 ,
112+ nWorker uint ,
113+ headers storage.Headers ,
114+ chunkDataPacks storage.ChunkDataPacks ,
115+ results storage.ExecutionResults ,
116+ state protocol.State ,
117+ verifier module.ChunkVerifier ,
118+ verifyHeight func (uint64 , storage.Headers , storage.ChunkDataPacks , storage.ExecutionResults , protocol.State , module.ChunkVerifier ) error ,
119+ ) error {
120+ tasks := make (chan uint64 , int (nWorker ))
121+ ctx , cancel := context .WithCancel (context .Background ())
122+ defer cancel () // Ensure cancel is called to release resources
123+
124+ var lowestErr error
125+ var lowestErrHeight uint64 = ^ uint64 (0 ) // Initialize to max value of uint64
126+ var mu sync.Mutex // To protect access to lowestErr and lowestErrHeight
127+
128+ // Worker function
129+ worker := func () {
130+ for {
131+ select {
132+ case <- ctx .Done ():
133+ return // Stop processing tasks if context is canceled
134+ case height , ok := <- tasks :
135+ if ! ok {
136+ return // Exit if the tasks channel is closed
137+ }
138+ log .Info ().Uint64 ("height" , height ).Msg ("verifying height" )
139+ err := verifyHeight (height , headers , chunkDataPacks , results , state , verifier )
140+ if err != nil {
141+ log .Error ().Uint64 ("height" , height ).Err (err ).Msg ("error encountered while verifying height" )
142+
143+ // when encountered an error, the error might not be from the lowest height that had
144+ // error, so we need to first cancel the context to stop worker from processing further tasks
145+ // and wait until all workers are done, which will ensure all the heights before this height
146+ // that had error are processed. Then we can safely update the lowestErr and lowestErrHeight
147+ mu .Lock ()
148+ if height < lowestErrHeight {
149+ lowestErr = err
150+ lowestErrHeight = height
151+ cancel () // Cancel context to stop further task dispatch
152+ }
153+ mu .Unlock ()
154+ } else {
155+ log .Info ().Uint64 ("height" , height ).Msg ("verified height successfully" )
156+ }
157+ }
158+ }
159+ }
160+
161+ // Start nWorker workers
162+ var wg sync.WaitGroup
163+ for i := 0 ; i < int (nWorker ); i ++ {
164+ wg .Add (1 )
165+ go func () {
166+ defer wg .Done ()
167+ worker ()
168+ }()
169+ }
170+
171+ // Send tasks to workers
172+ go func () {
173+ defer close (tasks ) // Close tasks channel once all tasks are pushed
174+ for height := from ; height <= to ; height ++ {
175+ select {
176+ case <- ctx .Done ():
177+ return // Stop pushing tasks if context is canceled
178+ case tasks <- height :
179+ }
107180 }
181+ }()
182+
183+ // Wait for all workers to complete
184+ wg .Wait ()
185+
186+ // Check if there was an error
187+ if lowestErr != nil {
188+ log .Error ().Uint64 ("height" , lowestErrHeight ).Err (lowestErr ).Msg ("error encountered while verifying height" )
189+ return fmt .Errorf ("could not verify height %d: %w" , lowestErrHeight , lowestErr )
108190 }
109191
110192 return nil
0 commit comments