Skip to content

Commit 0fb30f6

Browse files
committed
feat(evmreader): fast sync application inputs
This is an optimization to decrease the number of blocks the node has to scan for an new application. By retrieving zero from GetNumberOfInputs call, we can skip scanning the blocks previous to this, thus reducing the total block range.
1 parent 85ee681 commit 0fb30f6

7 files changed

Lines changed: 117 additions & 8 deletions

File tree

internal/evmreader/application_adapter.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,7 @@ func (a *ApplicationContractAdapterImpl) RetrieveOutputExecutionEvents(
9595
}
9696
return events, nil
9797
}
98+
99+
func (a *ApplicationContractAdapterImpl) GetDeploymentBlockNumber(opts *bind.CallOpts) (*big.Int, error) {
100+
return a.application.GetDeploymentBlockNumber(opts)
101+
}

internal/evmreader/evmreader.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type ApplicationContractAdapter interface {
5757
RetrieveOutputExecutionEvents(
5858
opts *bind.FilterOpts,
5959
) ([]*iapplication.IApplicationOutputExecuted, error)
60+
GetDeploymentBlockNumber(opts *bind.CallOpts) (*big.Int, error)
6061
}
6162

6263
// Interface for Input reading
@@ -65,6 +66,7 @@ type InputSourceAdapter interface {
6566
// by go-ethereum and cannot be used for testing
6667
RetrieveInputs(opts *bind.FilterOpts, appAddresses []common.Address, index []*big.Int,
6768
) ([]iinputbox.IInputBoxInputAdded, error)
69+
GetNumberOfInputs(opts *bind.CallOpts, appContract common.Address) (*big.Int, error)
6870
}
6971

7072
type SubscriptionError struct {

internal/evmreader/evmreader_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,11 @@ func (m *MockInputBox) RetrieveInputs(
377377
return args.Get(0).([]iinputbox.IInputBoxInputAdded), args.Error(1)
378378
}
379379

380+
func (m *MockInputBox) GetNumberOfInputs(opts *bind.CallOpts, appContract common.Address) (*big.Int, error) {
381+
args := m.Called(opts, appContract)
382+
return args.Get(0).(*big.Int), args.Error(1)
383+
}
384+
380385
// Mock InputReaderRepository
381386
type MockRepository struct {
382387
mock.Mock
@@ -567,6 +572,11 @@ func (m *MockApplicationContract) RetrieveOutputExecutionEvents(
567572
return args.Get(0).([]*iapplication.IApplicationOutputExecuted), args.Error(1)
568573
}
569574

575+
func (m *MockApplicationContract) GetDeploymentBlockNumber(opts *bind.CallOpts) (*big.Int, error) {
576+
args := m.Called(opts)
577+
return args.Get(0).(*big.Int), args.Error(1)
578+
}
579+
570580
type MockAdapterFactory struct {
571581
mock.Mock
572582
}

internal/evmreader/input.go

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"errors"
99
"fmt"
10+
"math/big"
1011

1112
. "github.com/cartesi/rollups-node/internal/model"
1213
"github.com/ethereum/go-ethereum/accounts/abi/bind"
@@ -25,6 +26,50 @@ func (r *Service) checkForNewInputs(
2526

2627
r.Logger.Debug("Checking for new inputs")
2728

29+
mostRecentBlockNumberCallOpts := &bind.CallOpts{
30+
BlockNumber: new(big.Int).SetUint64(mostRecentBlockNumber),
31+
}
32+
33+
findFirstInputBlock := func(app *appContracts) uint64 {
34+
var noiBig *big.Int
35+
var err error
36+
37+
// application has never received an input
38+
noiBig, err = app.inputSource.GetNumberOfInputs(
39+
mostRecentBlockNumberCallOpts,
40+
app.application.IApplicationAddress,
41+
)
42+
if err != nil {
43+
return app.application.LastInputCheckBlock
44+
}
45+
if noiBig.Uint64() == 0 {
46+
return mostRecentBlockNumber
47+
}
48+
49+
// application has not received an input since its deployment
50+
// but find its deployment block number first
51+
deploymentBlockNumberBig, err := app.applicationContract.GetDeploymentBlockNumber(mostRecentBlockNumberCallOpts)
52+
if err != nil {
53+
return app.application.LastInputCheckBlock
54+
}
55+
56+
noiBig, err = app.inputSource.GetNumberOfInputs(&bind.CallOpts{
57+
BlockNumber: deploymentBlockNumberBig,
58+
},
59+
app.application.IApplicationAddress,
60+
)
61+
if err != nil {
62+
return app.application.LastInputCheckBlock
63+
}
64+
if noiBig.Uint64() == 0 {
65+
return deploymentBlockNumberBig.Uint64()
66+
}
67+
68+
// TODO(mpolitzer): use GetNumberOfInputs to binary search the
69+
// block of the first input
70+
return app.application.IInputBoxBlock
71+
}
72+
2873
appsByInputBox := map[common.Address][]appContracts{}
2974
for _, app := range applications {
3075
if !app.application.HasDataAvailabilitySelector(DataAvailability_InputBox) {
@@ -39,18 +84,24 @@ func (r *Service) checkForNewInputs(
3984
"inputbox_address", inputBoxAddress,
4085
"most recent block", mostRecentBlockNumber,
4186
)
42-
appsByLastInputCheckBlock := indexApps(byLastInputCheckBlock, inputBoxApps)
87+
88+
appsByLastInputCheckBlock := make(map[uint64][]appContracts)
89+
for _, app := range inputBoxApps {
90+
i := app.application.LastInputCheckBlock
91+
if i == 0 { // fast sync applications with no inputs
92+
i = findFirstInputBlock(&app)-1
93+
r.Logger.Info("Fast sync application inputs",
94+
"application", app.application.Name,
95+
"start_block", i,
96+
)
97+
app.application.LastInputCheckBlock = i
98+
}
99+
appsByLastInputCheckBlock[i] = append(appsByLastInputCheckBlock[i], app)
100+
}
43101

44102
for lastProcessedBlock, apps := range appsByLastInputCheckBlock {
45103
appAddresses := appsToAddresses(apps)
46104

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-
54105
if mostRecentBlockNumber > lastProcessedBlock {
55106

56107
r.Logger.Debug("Checking inputs for applications",

internal/evmreader/input_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package evmreader
55

66
import (
7+
"math/big"
78
"time"
89

910
. "github.com/cartesi/rollups-node/internal/model"
@@ -129,6 +130,24 @@ func (s *EvmReaderSuite) TestItReadsInputsFromNewBlocksFilteredByDA() {
129130
mock.Anything,
130131
).Return(events_1, nil)
131132

133+
inputBox.Unset("GetNumberOfInputs")
134+
inputBox.On(
135+
"GetNumberOfInputs",
136+
mock.Anything,
137+
mock.Anything,
138+
).Return(new(big.Int).SetUint64(2), nil)
139+
140+
applicationContract.On(
141+
"GetDeploymentBlockNumber",
142+
mock.Anything,
143+
).Return(new(big.Int).SetUint64(10), nil)
144+
145+
inputBox.On(
146+
"GetNumberOfInputs",
147+
mock.Anything,
148+
mock.Anything,
149+
).Return(new(big.Int).SetUint64(0), nil)
150+
132151
// Start service
133152
ready := make(chan struct{}, 1)
134153
errChannel := make(chan error, 1)
@@ -267,6 +286,24 @@ func (s *EvmReaderSuite) TestItUpdatesLastInputCheckBlockWhenThereIsNoInputs() {
267286
mock.Anything,
268287
).Return(events_0, nil)
269288

289+
inputBox.Unset("GetNumberOfInputs")
290+
inputBox.On(
291+
"GetNumberOfInputs",
292+
mock.Anything,
293+
mock.Anything,
294+
).Return(new(big.Int).SetUint64(1), nil)
295+
296+
applicationContract.On(
297+
"GetDeploymentBlockNumber",
298+
mock.Anything,
299+
).Return(new(big.Int).SetUint64(10), nil)
300+
301+
inputBox.On(
302+
"GetNumberOfInputs",
303+
mock.Anything,
304+
mock.Anything,
305+
).Return(new(big.Int).SetUint64(0), nil)
306+
270307
events_1 := []iinputbox.IInputBoxInputAdded{}
271308
mostRecentBlockNumber_1 := uint64(0x12)
272309
retrieveInputsOpts_1 := bind.FilterOpts{

internal/evmreader/inputsource_adapter.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,7 @@ func (i *InputSourceAdapterImpl) RetrieveInputs(
110110
}
111111
return events, nil
112112
}
113+
114+
func (i *InputSourceAdapterImpl) GetNumberOfInputs(opts *bind.CallOpts, addr common.Address) (*big.Int, error) {
115+
return i.inputbox.GetNumberOfInputs(opts, addr)
116+
}

internal/evmreader/output_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func (s *EvmReaderSuite) TestOutputExecution() {
3939
DataAvailability: DataAvailability_InputBox[:],
4040
IInputBoxBlock: 0x10,
4141
EpochLength: 10,
42+
LastInputCheckBlock: 0x01, // don't fast sync inputs
4243
LastOutputCheckBlock: 0x10,
4344
}}, uint64(1), nil).Once()
4445
s.repository.On(

0 commit comments

Comments
 (0)