@@ -7,12 +7,152 @@ import (
77 "context"
88 "errors"
99 "fmt"
10+ "math/big"
11+ "sort"
1012
1113 . "github.com/cartesi/rollups-node/internal/model"
1214 "github.com/ethereum/go-ethereum/accounts/abi/bind"
1315 "github.com/ethereum/go-ethereum/common"
1416)
1517
18+ // readInputsOnMachingBlocks fetches the inputAdded events from matching the blocks
19+ // on the blockchain they appear instead of searching for a range.
20+ func (r * Service ) readInputsOnMachingBlocks (
21+ ctx context.Context ,
22+ app * appContracts ,
23+ blocks []uint64 ,
24+ inputSource InputSourceAdapter ,
25+ ) ([]* Input , error ) {
26+ inputs := []* Input {}
27+
28+ for i , block := range blocks {
29+ if (i > 0 ) && (blocks [i - 1 ] == block ) { // skip repetitions
30+ continue
31+ }
32+ opts := bind.FilterOpts {
33+ Context : ctx ,
34+ Start : block ,
35+ End : & block ,
36+ }
37+ inputsEvents , err := inputSource .RetrieveInputs (
38+ & opts ,
39+ []common.Address {app .application .IApplicationAddress },
40+ nil ,
41+ )
42+ if err != nil {
43+ return nil , fmt .Errorf ("failed to retrieve inputs: %w" , err )
44+ }
45+
46+ // NOTE: there may be more than one input in the same block
47+ for _ , event := range inputsEvents {
48+ r .Logger .Debug ("Received input" ,
49+ "address" , event .AppContract ,
50+ "index" , event .Index ,
51+ "block" , event .Raw .BlockNumber )
52+ input := & Input {
53+ Index : event .Index .Uint64 (),
54+ Status : InputCompletionStatus_None ,
55+ RawData : event .Input ,
56+ BlockNumber : event .Raw .BlockNumber ,
57+ TransactionReference : common .BigToHash (event .Index ),
58+ }
59+ inputs = append (inputs , input )
60+ }
61+ }
62+
63+ sort .Slice (inputs , func (i , j int ) bool {
64+ return inputs [i ].Index < inputs [j ].Index
65+ })
66+ return inputs , nil
67+ }
68+
69+ // fastSyncInputs finds inputs via getNumberOfInputs instead of reading the logs
70+ // this is cheaper when the logs span many blocks, especially when the application
71+ // has no inputs. In case there are inputs, search for the block in which they
72+ // appear with a binary search.
73+ func (r * Service ) fastSyncInputs (
74+ ctx context.Context ,
75+ lastProcessedBlock uint64 ,
76+ mostRecentBlockNumber uint64 ,
77+ app * appContracts ,
78+ ) ([]* Input , error ) {
79+ r .Logger .Debug ("Fast sync inputs" ,
80+ "application" , app .application .Name ,
81+ )
82+
83+ getNumberOfInputs := func (nr uint64 ) (uint64 , error ) {
84+ n , err := app .inputSource .GetNumberOfInputs (& bind.CallOpts {
85+ BlockNumber : new (big.Int ).SetUint64 (nr ),
86+ }, app .application .IApplicationAddress )
87+ if err != nil {
88+ return 0 , fmt .Errorf ("call to GetNumberOfInputs failed: %w" , err )
89+ }
90+ return n .Uint64 (), nil
91+ }
92+
93+ noi , err := getNumberOfInputs (mostRecentBlockNumber )
94+ if err != nil {
95+ r .Logger .Debug ("Fast sync failed, application will do a regular sync." ,
96+ "application" , app .application .IApplicationAddress ,
97+ "error" , err ,
98+ )
99+ return nil , err
100+ }
101+
102+ // application has no inputs, sync is done
103+ if noi == 0 {
104+ r .Logger .Info ("No inputs, fast sync done" ,
105+ "application" , app .application .Name ,
106+ )
107+ return nil , nil
108+ }
109+
110+ // application has inputs, find their blocks and read them in
111+ deployedAtBig , err := app .applicationContract .GetDeploymentBlockNumber (& bind.CallOpts {
112+ BlockNumber : new (big.Int ).SetUint64 (mostRecentBlockNumber ),
113+ })
114+ if err != nil {
115+ r .Logger .Info ("Fast sync failed, application will do a regular sync." ,
116+ "application" , app .application .IApplicationAddress ,
117+ "error" , err ,
118+ )
119+ return nil , err
120+ }
121+
122+ // assume that most inputs happen after this application deployment.
123+ // Do the first range split according to it. Fallback to searching the whole
124+ // range if the assumption is false.
125+ applicationDeploymentBlock := deployedAtBig .Uint64 ()
126+ noiOld , err := getNumberOfInputs (applicationDeploymentBlock )
127+ if err != nil {
128+ r .Logger .Debug ("Fast sync failed, application will do a regular sync." ,
129+ "application" , app .application .IApplicationAddress ,
130+ "error" , err ,
131+ )
132+ return nil , err
133+ }
134+
135+ startSearchBlock := applicationDeploymentBlock
136+ endSearchBlock := mostRecentBlockNumber
137+ if noiOld == 0 { // all inputs are newer than application deployment
138+ // use current values
139+ } else if noi == noiOld { // all inputs are older than application deployment
140+ startSearchBlock = app .application .IInputBoxBlock
141+ endSearchBlock = applicationDeploymentBlock
142+ } else { // there are both old and new inputs
143+ startSearchBlock = app .application .IInputBoxBlock
144+ }
145+
146+ inputsBlockNumbers , err := MBSearch (startSearchBlock , endSearchBlock , noi , getNumberOfInputs )
147+ r .Logger .Info ("Fast sync found inputs on the following blocks" ,
148+ "application" , app .application .Name ,
149+ "inputBlockNumbers" , inputsBlockNumbers ,
150+ )
151+
152+ inputs , err := r .readInputsOnMachingBlocks (ctx , app , inputsBlockNumbers , app .inputSource )
153+ return inputs , err
154+ }
155+
16156// checkForNewInputs checks if is there new Inputs for all running Applications
17157func (r * Service ) checkForNewInputs (
18158 ctx context.Context ,
@@ -44,15 +184,7 @@ func (r *Service) checkForNewInputs(
44184 for lastProcessedBlock , apps := range appsByLastInputCheckBlock {
45185 appAddresses := appsToAddresses (apps )
46186
47- // Only check blocks starting from the block where the InputBox
48- // contract was deployed as Inputs can be added to that same block
49- inputBoxDeploymentBlock := apps [0 ].application .IInputBoxBlock
50- if lastProcessedBlock < inputBoxDeploymentBlock {
51- lastProcessedBlock = inputBoxDeploymentBlock - 1
52- }
53-
54187 if mostRecentBlockNumber > lastProcessedBlock {
55-
56188 r .Logger .Debug ("Checking inputs for applications" ,
57189 "apps" , appAddresses ,
58190 "last_processed_block" , lastProcessedBlock ,
@@ -107,12 +239,39 @@ func (r *Service) readAndStoreInputs(
107239
108240 // Retrieve Inputs from blockchain
109241 nextSearchBlock := lastProcessedBlock + 1
110- appInputsMap , err := r .readInputsFromBlockchain (ctx , apps , nextSearchBlock , mostRecentBlockNumber )
111- if err != nil {
112- return fmt .Errorf ("failed to read inputs from block %v to block %v. %w" ,
113- nextSearchBlock ,
114- mostRecentBlockNumber ,
115- err )
242+ var appInputsMap = make (map [common.Address ][]* Input )
243+
244+ // try to fast sync
245+ if lastProcessedBlock == 0 {
246+ for _ , app := range apps {
247+ inputs , err := r .fastSyncInputs (ctx , lastProcessedBlock , mostRecentBlockNumber , & app )
248+ if err != nil {
249+ return fmt .Errorf ("failed to read inputs of application %v: %w" ,
250+ app .application .IApplicationAddress ,
251+ err ,
252+ )
253+ }
254+ appInputsMap [app .application .IApplicationAddress ] = inputs
255+ }
256+ lastProcessedBlock = mostRecentBlockNumber
257+ nextSearchBlock = mostRecentBlockNumber + 1
258+ } else {
259+ var err error
260+ // Only check blocks starting from the block where the InputBox
261+ // contract was deployed as Inputs can be added to that same block
262+ inputBoxDeploymentBlock := apps [0 ].application .IInputBoxBlock
263+ if lastProcessedBlock < inputBoxDeploymentBlock {
264+ lastProcessedBlock = inputBoxDeploymentBlock - 1
265+ }
266+ nextSearchBlock = lastProcessedBlock + 1 // update because we changed lastProcessedBlock
267+
268+ appInputsMap , err = r .readInputsFromBlockchain (ctx , apps , nextSearchBlock , mostRecentBlockNumber )
269+ if err != nil {
270+ return fmt .Errorf ("failed to read inputs from block %v to block %v. %w" ,
271+ nextSearchBlock ,
272+ mostRecentBlockNumber ,
273+ err )
274+ }
116275 }
117276
118277 addrToApp := mapAddressToApp (apps )
0 commit comments