From 839e3086f0237bbe44c6bfe8995823d0a6a23f2e Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:16:37 -0300 Subject: [PATCH 1/9] feat(claimer): respect DefaultBlock on FilterClaimSubmission --- internal/claimer/claimer.go | 7 +++-- internal/claimer/side-effects.go | 18 +++++++++++++ internal/claimer/util.go | 45 ++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 internal/claimer/util.go diff --git a/internal/claimer/claimer.go b/internal/claimer/claimer.go index a595114b9..b5639a9aa 100644 --- a/internal/claimer/claimer.go +++ b/internal/claimer/claimer.go @@ -64,8 +64,8 @@ type CreateInfo struct { Config config.Config - EthConn *ethclient.Client - Repository repository.Repository + EthConn *ethclient.Client + Repository repository.Repository } type Service struct { @@ -76,6 +76,7 @@ type Service struct { txOpts *bind.TransactOpts claimsInFlight map[common.Address]common.Hash // -> txHash submissionEnabled bool + defaultBlock config.DefaultBlock } const ClaimerConfigKey = "claimer" @@ -136,6 +137,8 @@ func Create(ctx context.Context, c *CreateInfo) (*Service, error) { return nil, err } } + s.defaultBlock = c.Config.BlockchainDefaultBlock + return s, nil } diff --git a/internal/claimer/side-effects.go b/internal/claimer/side-effects.go index 75c416bb2..fffc9fd82 100644 --- a/internal/claimer/side-effects.go +++ b/internal/claimer/side-effects.go @@ -199,9 +199,17 @@ func (s *Service) FindClaimSubmissionEventAndSucc( return nil, nil, nil, err } + // get the end block considering to block labels: finalized, latest... + endBig, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) + if err != nil { + return nil, nil, nil, err + } + endnr := endBig.Uint64() + it, err := ic.FilterClaimSubmission(&bind.FilterOpts{ Context: s.Context, Start: claim.LastBlock, + End: &endnr, }, nil, []common.Address{claim.IApplicationAddress}) if err != nil { return nil, nil, nil, err @@ -241,5 +249,15 @@ func (s *Service) PollTransaction(txHash common.Hash) (bool, *types.Receipt, err return false, nil, err } + // additionally wait for Default Block + // TODO: hoist this value out of "check computed claims" loop + endBig, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) + if err != nil { + return false, nil, err + } + if receipt.BlockNumber.Cmp(endBig) >= 0 { + return false, receipt, err + } + return receipt.Status == 1, receipt, err } diff --git a/internal/claimer/util.go b/internal/claimer/util.go new file mode 100644 index 000000000..274a2957d --- /dev/null +++ b/internal/claimer/util.go @@ -0,0 +1,45 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package claimer + +import ( + "context" + "fmt" + "math/big" + + "github.com/cartesi/rollups-node/internal/config" + "github.com/cartesi/rollups-node/internal/model" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" +) + +/* Retrieve the block number of "DefaultBlock" */ +func GetBlockNumber( + ctx context.Context, + client *ethclient.Client, + defaultBlock config.DefaultBlock, +) ( + *big.Int, + error, +) { + var nr int64 + switch defaultBlock { + case model.DefaultBlock_Pending: + nr = rpc.PendingBlockNumber.Int64() + case model.DefaultBlock_Latest: + nr = rpc.LatestBlockNumber.Int64() + case model.DefaultBlock_Finalized: + nr = rpc.FinalizedBlockNumber.Int64() + case model.DefaultBlock_Safe: + nr = rpc.SafeBlockNumber.Int64() + default: + return nil, fmt.Errorf("default block '%v' not supported", defaultBlock) + } + + hdr, err := client.HeaderByNumber(ctx, big.NewInt(nr)) + if err != nil { + return nil, err + } + return hdr.Number, nil +} From 1271fde71e0b819386498115e50e15cb2185a709 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:58:43 -0300 Subject: [PATCH 2/9] feat(claimer): FindClaimSubmissionEventAndSucc in chunks --- internal/claimer/side-effects.go | 81 ++++++++++++++++------- pkg/ethutil/filter.go | 108 +++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 24 deletions(-) create mode 100644 pkg/ethutil/filter.go diff --git a/internal/claimer/side-effects.go b/internal/claimer/side-effects.go index fffc9fd82..6539e3678 100644 --- a/internal/claimer/side-effects.go +++ b/internal/claimer/side-effects.go @@ -6,11 +6,14 @@ package claimer import ( "context" "fmt" + "iter" "math/big" . "github.com/cartesi/rollups-node/internal/model" "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" - "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/cartesi/rollups-node/pkg/ethutil" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -184,6 +187,22 @@ func (s *Service) pollTransaction(txHash common.Hash) (bool, *types.Receipt, err return ready, receipt, err } +func unwrapClaimSubmission( + ic *iconsensus.IConsensus, + pull func() (log *types.Log, err error, ok bool), +) ( + *iconsensus.IConsensusClaimSubmission, + bool, + error, +) { + log, err, ok := pull() + if !ok || err != nil { + return nil, false, err + } + ev, err := ic.ParseClaimSubmission(*log) + return ev, true, err +} + // scan the event stream for a claimSubmission event that matches claim. // return this event and its successor func (s *Service) FindClaimSubmissionEventAndSucc( @@ -199,42 +218,56 @@ func (s *Service) FindClaimSubmissionEventAndSucc( return nil, nil, nil, err } - // get the end block considering to block labels: finalized, latest... - endBig, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) + // filter must match: + // - `ClaimSubmission` events + // - submitter == nil (any) + // - appContract == claim.IApplicationAddress + c, err := iconsensus.IConsensusMetaData.GetAbi() + topics, err := abi.MakeTopics( + []interface{}{c.Events["ClaimSubmission"].ID}, + nil, + []interface{}{claim.IApplicationAddress}, + ) if err != nil { return nil, nil, nil, err } - endnr := endBig.Uint64() - it, err := ic.FilterClaimSubmission(&bind.FilterOpts{ - Context: s.Context, - Start: claim.LastBlock, - End: &endnr, - }, nil, []common.Address{claim.IApplicationAddress}) + endBig, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) + if err != nil { + return nil, nil, nil, err + } + it, err := ethutil.ChunkedFilterLogs(s.Context, s.ethConn, ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(claim.Epoch.LastBlock), + ToBlock: endBig, + Addresses: []common.Address{claim.IConsensusAddress}, + Topics: topics, + }) if err != nil { return nil, nil, nil, err } - for it.Next() { - event := it.Event + // pull events instead of iterating + next, stop := iter.Pull2(it) + defer stop() + for { + event, ok, err := unwrapClaimSubmission(ic, next) + if !ok || err != nil { + return ic, event, nil, err + } lastBlock := event.LastProcessedBlockNumber.Uint64() + if claimMatchesEvent(claim, event) { - var succ *iconsensus.IConsensusClaimSubmission = nil - if it.Next() { - succ = it.Event - } - if it.Error() != nil { - return nil, nil, nil, it.Error() + // found the event, does it has a successor? try to fetch it + succ, ok, err := unwrapClaimSubmission(ic, next) + if !ok || err != nil { + return ic, event, nil, err } - return ic, event, succ, nil - } else if lastBlock > claim.LastBlock { - err = fmt.Errorf("claim not found, searched up to %v", event) + return ic, event, succ, err + } else if lastBlock > claim.Epoch.LastBlock { + err = fmt.Errorf("No matching claim, searched up to %v", event) + return nil, nil, nil, err } } - if err := it.Error(); err != nil { - return nil, nil, nil, err - } - return ic, nil, nil, err } /* poll a transaction hash for its submission status and receipt */ diff --git a/pkg/ethutil/filter.go b/pkg/ethutil/filter.go new file mode 100644 index 000000000..99d2953d1 --- /dev/null +++ b/pkg/ethutil/filter.go @@ -0,0 +1,108 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) +package ethutil + +import ( + "context" + "iter" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" +) + +var ( + minChunk = new(big.Int).SetInt64(64) +) + +// read chunkedFilterLogs comment for additional information. +// +// NOTE: There is no standard reply among providers, add as needed. This +// function assumes that any server side error codes represent block range that +// is too large. +// ┌────────────────────────────┬───────┬────────┬────────────┐ +// │ provider │ limit │ code │ checked at │ +// ├────────────────────────────┼───────┼────────┼────────────┤ +// │ https://cloudflare-eth.com │ 800 │ -32047 │ 2025-01-24 │ +// └────────────────────────────┴───────┴────────┴────────────┘ +func queryBlockRangeTooLarge(err error) bool { + if err != nil { + switch e := err.(type) { + case rpc.Error: + return -32099 <= e.ErrorCode() && e.ErrorCode() <= -32000 + } + } + return false +} + +// chunkedFilterLogs is very similar to LogFilterer FilterLogs. Both functions +// query blockchain events (logs) and return the ones matching the filter +// criteria. In addition to the basic functionality, this version splits large +// (From, To) block ranges into multiple smaller calls when it detects the +// provider rejected the query for this specific reason. Detection is a +// heuristic and implemented in the function queryBlockRangeTooLarge. It +// potentially has to be adjusted to accomodate each provider. +func ChunkedFilterLogs( + ctx context.Context, + client *ethclient.Client, + q ethereum.FilterQuery, +) ( + iter.Seq2[*types.Log, error], + error, +) { + if q.FromBlock == nil { + q.FromBlock = big.NewInt(0) + } + if q.ToBlock == nil { + end, err := client.BlockNumber(ctx) + if err != nil { + return nil, err + } + q.ToBlock = big.NewInt(0).SetUint64(end) + } + + return func(yield func(log *types.Log, err error) bool) { + one := big.NewInt(1) + endBlock := new(big.Int).Set(q.ToBlock) + for q.FromBlock.Cmp(endBlock) <= 0 { + logs, err := client.FilterLogs(ctx, q) + delta := new(big.Int).Sub(q.ToBlock, q.FromBlock) + + if queryBlockRangeTooLarge(err) { + if delta.Cmp(minChunk) < 0 { + yield(nil, err) + return + } + // ToBlock -= ToBlock/2 + q.ToBlock.Sub(q.ToBlock, delta.Rsh(delta, 1)) + continue + } else if err != nil { + yield(nil, err) + return + } + + for _, log := range logs { + if !yield(&log, nil) { + return + } + } + + // ------------------------ + // [ delta | delta ] + // ^ ^ + // From To + // ------------------------ + // [ delta | delta ] + // ^ ^ + // From To + // ------------------------ + q.FromBlock.Add(q.ToBlock, one) + q.ToBlock.Add(q.FromBlock, delta) + if q.ToBlock.Cmp(endBlock) > 0 { + q.ToBlock.Set(endBlock) + } + } + }, nil +} From 3a8a7a5a8c9e0147c4614cafc9258bc2f87b05c1 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:18:17 -0300 Subject: [PATCH 3/9] feat(claimer): hoist GetBlockNumber up into Tick and propagate its value. --- internal/claimer/claimer.go | 18 +++++++--- internal/claimer/claimer_test.go | 56 +++++++++++++++++++------------- internal/claimer/side-effects.go | 30 ++++++++--------- 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/internal/claimer/claimer.go b/internal/claimer/claimer.go index b5639a9aa..9bd0128e4 100644 --- a/internal/claimer/claimer.go +++ b/internal/claimer/claimer.go @@ -40,6 +40,7 @@ package claimer import ( "context" "fmt" + "math/big" "github.com/cartesi/rollups-node/internal/config" "github.com/cartesi/rollups-node/internal/config/auth" @@ -159,10 +160,17 @@ func (s *Service) Stop(bool) []error { } func (s *Service) Tick() []error { - return s.submitClaimsAndUpdateDatabase(s) + errs := []error{} + endBlock, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) + if err != nil { + errs = append(errs, err) + return errs + } + + return s.submitClaimsAndUpdateDatabase(s, endBlock) } -func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { +func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.Int) []error { errs := []error{} prevClaims, currClaims, err := se.selectClaimPairsPerApp() if err != nil { @@ -172,7 +180,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { // check claims in flight for key, txHash := range s.claimsInFlight { - ready, receipt, err := se.pollTransaction(txHash) + ready, receipt, err := se.pollTransaction(txHash, endBlock) if err != nil { errs = append(errs, err) s.Logger.Warn("claim submission failed, retrying.", @@ -239,7 +247,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { // if prevClaimRow exists, there must be a matching event ic, prevEvent, currEvent, err = - se.findClaimSubmissionEventAndSucc(prevClaimRow) + se.findClaimSubmissionEventAndSucc(prevClaimRow, endBlock) if err != nil { delete(currClaims, key) errs = append(errs, err) @@ -285,7 +293,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects) []error { } else { // first claim ic, currEvent, _, err = - se.findClaimSubmissionEventAndSucc(currClaimRow) + se.findClaimSubmissionEventAndSucc(currClaimRow, endBlock) if err != nil { delete(currClaims, key) errs = append(errs, err) diff --git a/internal/claimer/claimer_test.go b/internal/claimer/claimer_test.go index dea39e4cd..2b647c217 100644 --- a/internal/claimer/claimer_test.go +++ b/internal/claimer/claimer_test.go @@ -52,13 +52,14 @@ func (m *serviceMock) updateApplicationState(appID int64, state ApplicationState func (m *serviceMock) findClaimSubmissionEventAndSucc( claim *ClaimRow, + endBlock *big.Int, ) ( *iconsensus.IConsensus, *iconsensus.IConsensusClaimSubmission, *iconsensus.IConsensusClaimSubmission, error, ) { - args := m.Called(claim) + args := m.Called(claim, endBlock) return args.Get(0).(*iconsensus.IConsensus), args.Get(1).(*iconsensus.IConsensusClaimSubmission), args.Get(2).(*iconsensus.IConsensusClaimSubmission), @@ -71,8 +72,8 @@ func (m *serviceMock) submitClaimToBlockchain( args := m.Called(nil, claim) return args.Get(0).(common.Hash), args.Error(1) } -func (m *serviceMock) pollTransaction(txHash common.Hash) (bool, *types.Receipt, error) { - args := m.Called(txHash) +func (m *serviceMock) pollTransaction(txHash common.Hash, endBlock *big.Int) (bool, *types.Receipt, error) { + args := m.Called(txHash, endBlock) return args.Bool(0), args.Get(1).(*types.Receipt), args.Error(2) @@ -109,12 +110,13 @@ func TestDoNothing(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, big.NewInt(0)) assert.Equal(t, len(errs), 0) } func TestSubmitFirstClaim(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") @@ -138,12 +140,12 @@ func TestSubmitFirstClaim(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &currClaim). + m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) m.On("submitClaimToBlockchain", nil, &currClaim). Return(claimTransactionHash, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 1) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) @@ -155,6 +157,7 @@ func TestSubmitFirstClaim(t *testing.T) { func TestSubmitClaimWithAntecessor(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") @@ -196,12 +199,12 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim). + m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) m.On("submitClaimToBlockchain", nil, &currClaim). Return(claimTransactionHash, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 1) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) @@ -213,6 +216,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { func TestSkipSubmitFirstClaim(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) m.submissionEnabled = false appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") @@ -237,12 +241,12 @@ func TestSkipSubmitFirstClaim(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &currClaim). + m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) m.On("submitClaimToBlockchain", nil, &currClaim). Return(claimTransactionHash, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) @@ -254,6 +258,7 @@ func TestSkipSubmitFirstClaim(t *testing.T) { func TestSkipSubmitClaimWithAntecessor(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) m.submissionEnabled = false appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") @@ -295,12 +300,12 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim). + m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) m.On("submitClaimToBlockchain", nil, &currClaim). Return(claimTransactionHash, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) @@ -312,6 +317,7 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { func TestInFlightCompleted(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") reqHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") @@ -334,7 +340,7 @@ func TestInFlightCompleted(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("pollTransaction", reqHash). + m.On("pollTransaction", reqHash, endBlock). Return(true, &types.Receipt{ ContractAddress: appContractAddress, TxHash: txHash, @@ -342,7 +348,7 @@ func TestInFlightCompleted(t *testing.T) { m.On("updateEpochWithSubmittedClaim", &currClaim, txHash). Return(nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 0) @@ -354,6 +360,7 @@ func TestInFlightCompleted(t *testing.T) { func TestUpdateFirstClaim(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") claimHash := common.HexToHash("0x100") currClaim := ClaimRow{ @@ -379,12 +386,12 @@ func TestUpdateFirstClaim(t *testing.T) { } m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &currClaim). + m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). Return(&iconsensus.IConsensus{}, &currEvent, nilEvent, nil) m.On("updateEpochWithSubmittedClaim", &currClaim, currEvent.Raw.TxHash). Return(nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) @@ -396,6 +403,7 @@ func TestUpdateFirstClaim(t *testing.T) { func TestUpdateClaimWithAntecessor(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") @@ -438,12 +446,12 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { } m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim). + m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, &prevEvent, &currEvent, nil) m.On("updateEpochWithSubmittedClaim", &currClaim, currEvent.Raw.TxHash). Return(nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) @@ -460,6 +468,7 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { // !claimMatchesEvent(prevClaim, prevEvent) func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") @@ -500,14 +509,14 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim). + m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) m.On("updateApplicationState", int64(0), model.ApplicationState_Inoperable, mock.Anything). Return(nil) m.On("submitClaimToBlockchain", nil, &currClaim). Return(claimTransactionHash, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 1) assert.Equal(t, errs[0], ErrEventMismatch) } @@ -515,6 +524,7 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { // !claimMatchesEvent(currClaim, currEvent) func TestSubmitClaimWithEventMismatch(t *testing.T) { m := newServiceMock() + endBlock := big.NewInt(0) appContractAddress := common.HexToAddress("0x01") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") @@ -557,14 +567,14 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { } m.On("selectClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim). + m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, &prevEvent, &currEvent, nil) m.On("updateApplicationState", int64(0), model.ApplicationState_Inoperable, mock.Anything). Return(nil) m.On("updateEpochWithSubmittedClaim", &currClaim, currEvent.Raw.TxHash). Return(nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 1) assert.Equal(t, errs[0], ErrEventMismatch) } @@ -619,7 +629,7 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { m.On("submitClaimToBlockchain", nil, &currClaim). Return(claimTransactionHash, nil) - errs := m.submitClaimsAndUpdateDatabase(m) + errs := m.submitClaimsAndUpdateDatabase(m, big.NewInt(0)) assert.Equal(t, len(errs), 1) assert.Equal(t, errs[0], ErrClaimMismatch) } diff --git a/internal/claimer/side-effects.go b/internal/claimer/side-effects.go index 6539e3678..01645015a 100644 --- a/internal/claimer/side-effects.go +++ b/internal/claimer/side-effects.go @@ -38,6 +38,7 @@ type sideEffects interface { // blockchain findClaimSubmissionEventAndSucc( claim *ClaimRow, + endBlock *big.Int, ) ( *iconsensus.IConsensus, *iconsensus.IConsensusClaimSubmission, @@ -51,7 +52,10 @@ type sideEffects interface { common.Hash, error, ) - pollTransaction(txHash common.Hash) ( + pollTransaction( + txHash common.Hash, + endBlock *big.Int, + ) ( bool, *types.Receipt, error, @@ -122,13 +126,14 @@ func (s *Service) updateApplicationState( func (s *Service) findClaimSubmissionEventAndSucc( claim *ClaimRow, + endBlock *big.Int, ) ( *iconsensus.IConsensus, *iconsensus.IConsensusClaimSubmission, *iconsensus.IConsensusClaimSubmission, error, ) { - ic, curr, next, err := s.FindClaimSubmissionEventAndSucc(claim) + ic, curr, next, err := s.FindClaimSubmissionEventAndSucc(claim, endBlock) if err != nil { s.Logger.Error("findClaimSubmissionEventAndSucc:failed", "claim", claim, @@ -168,8 +173,8 @@ func (s *Service) submitClaimToBlockchain( return txHash, err } -func (s *Service) pollTransaction(txHash common.Hash) (bool, *types.Receipt, error) { - ready, receipt, err := s.PollTransaction(txHash) +func (s *Service) pollTransaction(txHash common.Hash, endBlock *big.Int) (bool, *types.Receipt, error) { + ready, receipt, err := s.PollTransaction(txHash, endBlock) if err != nil { s.Logger.Error("PollTransaction:failed", "tx", txHash, @@ -207,6 +212,7 @@ func unwrapClaimSubmission( // return this event and its successor func (s *Service) FindClaimSubmissionEventAndSucc( claim *ClaimRow, + endBlock *big.Int, ) ( *iconsensus.IConsensus, *iconsensus.IConsensusClaimSubmission, @@ -232,13 +238,9 @@ func (s *Service) FindClaimSubmissionEventAndSucc( return nil, nil, nil, err } - endBig, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) - if err != nil { - return nil, nil, nil, err - } it, err := ethutil.ChunkedFilterLogs(s.Context, s.ethConn, ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(claim.Epoch.LastBlock), - ToBlock: endBig, + ToBlock: endBlock, Addresses: []common.Address{claim.IConsensusAddress}, Topics: topics, }) @@ -271,7 +273,7 @@ func (s *Service) FindClaimSubmissionEventAndSucc( } /* poll a transaction hash for its submission status and receipt */ -func (s *Service) PollTransaction(txHash common.Hash) (bool, *types.Receipt, error) { +func (s *Service) PollTransaction(txHash common.Hash, endBlock *big.Int) (bool, *types.Receipt, error) { _, isPending, err := s.ethConn.TransactionByHash(s.Context, txHash) if err != nil || isPending { return false, nil, err @@ -282,13 +284,7 @@ func (s *Service) PollTransaction(txHash common.Hash) (bool, *types.Receipt, err return false, nil, err } - // additionally wait for Default Block - // TODO: hoist this value out of "check computed claims" loop - endBig, err := GetBlockNumber(s.Context, s.ethConn, s.defaultBlock) - if err != nil { - return false, nil, err - } - if receipt.BlockNumber.Cmp(endBig) >= 0 { + if receipt.BlockNumber.Cmp(endBlock) >= 0 { return false, receipt, err } From a5b4f418b4aa80104ed63ac498f94ade1b194333 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Mon, 24 Feb 2025 19:18:37 -0300 Subject: [PATCH 4/9] feat: migrate claimAcceptance event handling to claimer --- internal/claimer/claimer.go | 211 +++++-- internal/claimer/claimer_test.go | 66 ++- internal/claimer/side-effects.go | 180 +++++- internal/evmreader/claim.go | 266 --------- internal/evmreader/claim_test.go | 740 ------------------------ internal/evmreader/evmreader.go | 2 - internal/repository/postgres/claimer.go | 99 +++- internal/repository/repository.go | 12 +- 8 files changed, 477 insertions(+), 1099 deletions(-) delete mode 100644 internal/evmreader/claim.go delete mode 100644 internal/evmreader/claim_test.go diff --git a/internal/claimer/claimer.go b/internal/claimer/claimer.go index 9bd0128e4..88f3a89ce 100644 --- a/internal/claimer/claimer.go +++ b/internal/claimer/claimer.go @@ -65,8 +65,8 @@ type CreateInfo struct { Config config.Config - EthConn *ethclient.Client - Repository repository.Repository + EthConn *ethclient.Client + Repository repository.Repository } type Service struct { @@ -167,12 +167,16 @@ func (s *Service) Tick() []error { return errs } - return s.submitClaimsAndUpdateDatabase(s, endBlock) + errs = append(errs, s.submitClaimsAndUpdateDatabase(s, endBlock)...) + errs = append(errs, s.acceptClaimsAndUpdateDatabase(s, endBlock)...) + + return errs } +/* transition claims from computed to submitted */ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.Int) []error { errs := []error{} - prevClaims, currClaims, err := se.selectClaimPairsPerApp() + acceptedOrSubmittedClaims, computedClaims, err := se.selectSubmissionClaimPairsPerApp() if err != nil { errs = append(errs, err) return errs @@ -182,18 +186,17 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In for key, txHash := range s.claimsInFlight { ready, receipt, err := se.pollTransaction(txHash, endBlock) if err != nil { - errs = append(errs, err) - s.Logger.Warn("claim submission failed, retrying.", + s.Logger.Warn("Claim submission failed, retrying.", "txHash", txHash, "err", err, - ) + ) delete(s.claimsInFlight, key) continue } if !ready { continue } - if claim, ok := currClaims[key]; ok { + if claim, ok := computedClaims[key]; ok { err = se.updateEpochWithSubmittedClaim(claim, receipt.TxHash) if err != nil { errs = append(errs, err) @@ -201,10 +204,11 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In } s.Logger.Info("Claim submitted", "app", claim.IApplicationAddress, + "receipt_block_number", receipt.BlockNumber, "claim_hash", fmt.Sprintf("%x", claim.ClaimHash), "last_block", claim.LastBlock, "tx", txHash) - delete(currClaims, key) + delete(computedClaims, key) } else { s.Logger.Warn("expected claim in flight to be in currClaims.", "tx", receipt.TxHash) @@ -213,7 +217,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In } // check computed claims - for key, currClaimRow := range currClaims { + for key, computedClaim := range computedClaims { var ic *iconsensus.IConsensus = nil var prevEvent *iconsensus.IConsensusClaimSubmission = nil var currEvent *iconsensus.IConsensusClaimSubmission = nil @@ -222,16 +226,16 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In continue } - prevClaimRow, prevExists := prevClaims[key] + prevClaimRow, prevExists := acceptedOrSubmittedClaims[key] if prevExists { - err := checkClaimsConstraint(prevClaimRow, currClaimRow) + err := checkClaimsConstraint(prevClaimRow, computedClaim) if err != nil { s.Logger.Error("database mismatch", "prevClaim", prevClaimRow, - "currClaim", currClaimRow, + "currClaim", computedClaim, "err", err, ) - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, err) // update application state to inoperable err = se.updateApplicationState( @@ -249,7 +253,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In ic, prevEvent, currEvent, err = se.findClaimSubmissionEventAndSucc(prevClaimRow, endBlock) if err != nil { - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, err) goto nextApp } @@ -258,7 +262,7 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In "claim", prevClaimRow, "err", ErrMissingEvent, ) - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, ErrMissingEvent) // update application state to inoperable err = se.updateApplicationState( @@ -271,13 +275,13 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In } goto nextApp } - if !claimMatchesEvent(prevClaimRow, prevEvent) { + if !claimSubmissionMatch(prevClaimRow, prevEvent) { s.Logger.Error("event mismatch", "claim", prevClaimRow, "event", prevEvent, "err", ErrEventMismatch, ) - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, ErrEventMismatch) // update application state to inoperable err = se.updateApplicationState( @@ -293,9 +297,9 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In } else { // first claim ic, currEvent, _, err = - se.findClaimSubmissionEventAndSucc(currClaimRow, endBlock) + se.findClaimSubmissionEventAndSucc(computedClaim, endBlock) if err != nil { - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, err) goto nextApp } @@ -307,17 +311,17 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In "claim_hash", fmt.Sprintf("%x", currEvent.Claim), "last_block", currEvent.LastProcessedBlockNumber.Uint64(), ) - if !claimMatchesEvent(currClaimRow, currEvent) { + if !claimSubmissionMatch(computedClaim, currEvent) { s.Logger.Error("event mismatch", - "claim", currClaimRow, + "claim", computedClaim, "event", currEvent, "err", ErrEventMismatch, ) - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, ErrEventMismatch) // update application state to inoperable err = se.updateApplicationState( - currClaimRow.ApplicationID, + computedClaim.ApplicationID, ApplicationState_Inoperable, Pointer(ErrEventMismatch.Error()), ) @@ -327,22 +331,23 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In goto nextApp } s.Logger.Debug("Updating claim status to submitted", - "app", currClaimRow.IApplicationAddress, - "claim_hash", fmt.Sprintf("%x", currClaimRow.ClaimHash), - "last_block", currClaimRow.LastBlock, + "app", computedClaim.IApplicationAddress, + "claim_hash", fmt.Sprintf("%x", computedClaim.ClaimHash), + "last_block", computedClaim.LastBlock, ) txHash := currEvent.Raw.TxHash - err = se.updateEpochWithSubmittedClaim(currClaimRow, txHash) + err = se.updateEpochWithSubmittedClaim(computedClaim, txHash) if err != nil { - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, err) goto nextApp } delete(s.claimsInFlight, key) s.Logger.Info("Claim previously submitted", - "app", currClaimRow.IApplicationAddress, - "claim_hash", fmt.Sprintf("%x", currClaimRow.ClaimHash), - "last_block", currClaimRow.LastBlock, + "app", computedClaim.IApplicationAddress, + "event_block_number", currEvent.Raw.BlockNumber, + "claim_hash", fmt.Sprintf("%x", computedClaim.ClaimHash), + "last_block", computedClaim.LastBlock, ) } else if s.submissionEnabled { if prevClaimRow != nil && prevClaimRow.Status != EpochStatus_ClaimAccepted { @@ -354,22 +359,22 @@ func (s *Service) submitClaimsAndUpdateDatabase(se sideEffects, endBlock *big.In goto nextApp } s.Logger.Debug("Submitting claim to blockchain", - "app", currClaimRow.IApplicationAddress, - "claim_hash", fmt.Sprintf("%x", currClaimRow.ClaimHash), - "last_block", currClaimRow.LastBlock, + "app", computedClaim.IApplicationAddress, + "claim_hash", fmt.Sprintf("%x", computedClaim.ClaimHash), + "last_block", computedClaim.LastBlock, ) - txHash, err := se.submitClaimToBlockchain(ic, currClaimRow) + txHash, err := se.submitClaimToBlockchain(ic, computedClaim) if err != nil { - delete(currClaims, key) + delete(computedClaims, key) errs = append(errs, err) goto nextApp } s.claimsInFlight[key] = txHash } else { s.Logger.Debug("Claim submission disabled. Doing nothing", - "app", currClaimRow.IApplicationAddress, - "claim_hash", fmt.Sprintf("%x", currClaimRow.ClaimHash), - "last_block", currClaimRow.LastBlock, + "app", computedClaim.IApplicationAddress, + "claim_hash", fmt.Sprintf("%x", computedClaim.ClaimHash), + "last_block", computedClaim.LastBlock, ) } @@ -406,6 +411,114 @@ func (s *Service) setupPersistentConfig( s.Logger.Error("Could not retrieve persistent config from Database. %w", "error", err) return nil, err } + +/* transition claims from submitted to accepted */ +func (s *Service) acceptClaimsAndUpdateDatabase(se sideEffects, endBlock *big.Int) []error { + errs := []error{} + acceptedClaims, submittedClaims, err := se.selectAcceptanceClaimPairsPerApp() + if err != nil { + errs = append(errs, err) + return errs + } + + // check submitted claims + for key, submittedClaim := range submittedClaims { + var prevEvent *iconsensus.IConsensusClaimAcceptance = nil + var currEvent *iconsensus.IConsensusClaimAcceptance = nil + + acceptedClaim, prevExists := acceptedClaims[key] + if prevExists { + err := checkClaimsConstraint(acceptedClaim, submittedClaim) + if err != nil { + s.Logger.Error("database mismatch", + "prevClaim", acceptedClaim, + "currClaim", submittedClaim, + "err", err, + ) + delete(submittedClaims, key) + errs = append(errs, err) + goto nextApp + } + + // if prevClaimRow exists, there must be a matching event + _, prevEvent, currEvent, err = + se.findClaimAcceptanceEventAndSucc(acceptedClaim, endBlock) + if err != nil { + delete(submittedClaims, key) + errs = append(errs, err) + goto nextApp + } + if prevEvent == nil { + s.Logger.Error("missing event", + "claim", acceptedClaim, + "err", ErrMissingEvent, + ) + delete(submittedClaims, key) + errs = append(errs, ErrMissingEvent) + goto nextApp + } + if !claimAcceptanceMatch(acceptedClaim, prevEvent) { + s.Logger.Error("event mismatch", + "claim", acceptedClaim, + "event", prevEvent, + "err", ErrEventMismatch, + ) + delete(submittedClaims, key) + errs = append(errs, ErrEventMismatch) + goto nextApp + } + } else { + // first claim + _, currEvent, _, err = + se.findClaimAcceptanceEventAndSucc(submittedClaim, endBlock) + if err != nil { + delete(submittedClaims, key) + errs = append(errs, err) + goto nextApp + } + } + + if currEvent != nil { + s.Logger.Debug("Found ClaimAccepted Event", + "app", currEvent.AppContract, + "claim_hash", fmt.Sprintf("%x", currEvent.Claim), + "last_block", currEvent.LastProcessedBlockNumber.Uint64(), + ) + if !claimAcceptanceMatch(submittedClaim, currEvent) { + s.Logger.Error("event mismatch", + "claim", submittedClaim, + "event", currEvent, + "err", ErrEventMismatch, + ) + delete(submittedClaims, key) + errs = append(errs, ErrEventMismatch) + goto nextApp + } + s.Logger.Debug("Updating claim status to accepted", + "app", submittedClaim.IApplicationAddress, + "claim_hash", fmt.Sprintf("%x", submittedClaim.ClaimHash), + "last_block", submittedClaim.LastBlock, + ) + txHash := currEvent.Raw.TxHash + err = se.updateEpochWithAcceptedClaim(submittedClaim, txHash) + if err != nil { + delete(submittedClaims, key) + errs = append(errs, err) + goto nextApp + } + s.Logger.Info("Claim accepted", + "app", currEvent.AppContract, + "event_block_number", currEvent.Raw.BlockNumber, + "claim_hash", fmt.Sprintf("%x", currEvent.Claim), + "last_block", currEvent.LastProcessedBlockNumber.Uint64(), + "tx", txHash, + ) + } + nextApp: + } + return errs +} + func checkClaimConstraint(c *ClaimRow) error { zeroAddress := common.Address{} @@ -415,6 +528,16 @@ func checkClaimConstraint(c *ClaimRow) error { if c.IConsensusAddress == zeroAddress { return ErrClaimMismatch } + if c.Status == EpochStatus_ClaimSubmitted { + if c.ClaimHash == nil { + return ErrClaimMismatch + } + } + if c.Status == EpochStatus_ClaimAccepted || c.Status == EpochStatus_ClaimSubmitted { + if c.ClaimTransactionHash == nil { + return ErrClaimMismatch + } + } return nil } @@ -446,7 +569,13 @@ func checkClaimsConstraint(p *ClaimRow, c *ClaimRow) error { return nil } -func claimMatchesEvent(c *ClaimRow, e *iconsensus.IConsensusClaimSubmission) bool { +func claimSubmissionMatch(c *ClaimRow, e *iconsensus.IConsensusClaimSubmission) bool { + return c.IApplicationAddress == e.AppContract && + *c.ClaimHash == e.Claim && + c.LastBlock == e.LastProcessedBlockNumber.Uint64() +} + +func claimAcceptanceMatch(c *ClaimRow, e *iconsensus.IConsensusClaimAcceptance) bool { return c.IApplicationAddress == e.AppContract && *c.ClaimHash == e.Claim && c.LastBlock == e.LastProcessedBlockNumber.Uint64() diff --git a/internal/claimer/claimer_test.go b/internal/claimer/claimer_test.go index 2b647c217..3c9ca0cd1 100644 --- a/internal/claimer/claimer_test.go +++ b/internal/claimer/claimer_test.go @@ -27,7 +27,7 @@ type serviceMock struct { Service } -func (m *serviceMock) selectClaimPairsPerApp() ( +func (m *serviceMock) selectSubmissionClaimPairsPerApp() ( map[common.Address]*ClaimRow, map[common.Address]*ClaimRow, error, @@ -65,6 +65,7 @@ func (m *serviceMock) findClaimSubmissionEventAndSucc( args.Get(2).(*iconsensus.IConsensusClaimSubmission), args.Error(3) } + func (m *serviceMock) submitClaimToBlockchain( instance *iconsensus.IConsensus, claim *ClaimRow, @@ -107,7 +108,7 @@ func TestDoNothing(t *testing.T) { prevClaims := map[common.Address]*ClaimRow{} currClaims := map[common.Address]*ClaimRow{} - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) errs := m.submitClaimsAndUpdateDatabase(m, big.NewInt(0)) @@ -128,6 +129,7 @@ func TestSubmitFirstClaim(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -138,7 +140,7 @@ func TestSubmitFirstClaim(t *testing.T) { appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) @@ -150,7 +152,7 @@ func TestSubmitFirstClaim(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 1) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 1) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } @@ -162,6 +164,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") + prevClaimTxHash := common.HexToHash("0x102") prevClaim := ClaimRow{ IApplicationAddress: appContractAddress, IConsensusAddress: appContractAddress, @@ -170,6 +173,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &prevClaimHash, + ClaimTransactionHash: &prevClaimTxHash, Status: model.EpochStatus_ClaimAccepted, }, } @@ -181,6 +185,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -197,7 +202,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) @@ -209,7 +214,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 1) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 1) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } @@ -229,6 +234,7 @@ func TestSkipSubmitFirstClaim(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -239,7 +245,7 @@ func TestSkipSubmitFirstClaim(t *testing.T) { appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) @@ -251,7 +257,7 @@ func TestSkipSubmitFirstClaim(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } @@ -264,6 +270,7 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") + prevClaimTxHash := common.HexToHash("0x102") prevClaim := ClaimRow{ IApplicationAddress: appContractAddress, IConsensusAddress: appContractAddress, @@ -272,6 +279,8 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &prevClaimHash, + ClaimTransactionHash: &prevClaimTxHash, + Status: model.EpochStatus_ClaimAccepted, }, } currClaim := ClaimRow{ @@ -282,6 +291,7 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -298,7 +308,7 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) @@ -310,7 +320,7 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } @@ -330,6 +340,7 @@ func TestInFlightCompleted(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } prevClaims := map[common.Address]*ClaimRow{} @@ -338,7 +349,7 @@ func TestInFlightCompleted(t *testing.T) { } m.claimsInFlight[appContractAddress] = reqHash - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("pollTransaction", reqHash, endBlock). Return(true, &types.Receipt{ @@ -353,7 +364,7 @@ func TestInFlightCompleted(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 0) m.AssertNumberOfCalls(t, "pollTransaction", 1) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 1) } @@ -371,6 +382,7 @@ func TestUpdateFirstClaim(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -384,7 +396,7 @@ func TestUpdateFirstClaim(t *testing.T) { currClaims := map[common.Address]*ClaimRow{ appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). Return(&iconsensus.IConsensus{}, &currEvent, nilEvent, nil) @@ -396,7 +408,7 @@ func TestUpdateFirstClaim(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 1) } @@ -407,6 +419,7 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") + prevClaimTxHash := common.HexToHash("0x102") prevClaim := ClaimRow{ IApplicationAddress: appContractAddress, IConsensusAddress: appContractAddress, @@ -415,6 +428,8 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &prevClaimHash, + ClaimTransactionHash: &prevClaimTxHash, + Status: model.EpochStatus_ClaimAccepted, }, } currClaim := ClaimRow{ @@ -425,6 +440,7 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -444,7 +460,7 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { currClaims := map[common.Address]*ClaimRow{ appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, &prevEvent, &currEvent, nil) @@ -456,7 +472,7 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { assert.Equal(t, len(m.claimsInFlight), 0) m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 1) } @@ -473,6 +489,7 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") + prevClaimTxHash := common.HexToHash("0x102") prevClaim := ClaimRow{ IApplicationAddress: appContractAddress, IConsensusAddress: appContractAddress, @@ -481,6 +498,8 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &prevClaimHash, + ClaimTransactionHash: &prevClaimTxHash, + Status: model.EpochStatus_ClaimAccepted, }, } currClaim := ClaimRow{ @@ -491,6 +510,7 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -507,7 +527,7 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) @@ -528,6 +548,7 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { appContractAddress := common.HexToAddress("0x01") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") + prevClaimTxHash := common.HexToHash("0x102") prevClaim := ClaimRow{ IApplicationAddress: appContractAddress, IConsensusAddress: appContractAddress, @@ -536,6 +557,8 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &prevClaimHash, + ClaimTransactionHash: &prevClaimTxHash, + Status: model.EpochStatus_ClaimAccepted, }, } currClaim := ClaimRow{ @@ -546,6 +569,7 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { FirstBlock: 30, LastBlock: 39, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -565,7 +589,7 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { currClaims := map[common.Address]*ClaimRow{ appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). Return(&iconsensus.IConsensus{}, &prevEvent, &currEvent, nil) @@ -586,6 +610,7 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { claimTransactionHash := common.HexToHash("0x10") claimHash := common.HexToHash("0x100") prevClaimHash := common.HexToHash("0x101") + prevClaimTxHash := common.HexToHash("0x102") prevClaim := ClaimRow{ IApplicationAddress: appContractAddress, IConsensusAddress: appContractAddress, @@ -594,6 +619,8 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { FirstBlock: 20, LastBlock: 29, ClaimHash: &prevClaimHash, + ClaimTransactionHash: &prevClaimTxHash, + Status: model.EpochStatus_ClaimAccepted, }, } currClaim := ClaimRow{ @@ -604,6 +631,7 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { FirstBlock: 10, LastBlock: 19, ClaimHash: &claimHash, + Status: model.EpochStatus_ClaimComputed, }, } @@ -620,7 +648,7 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { appContractAddress: &currClaim, } - m.On("selectClaimPairsPerApp"). + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("findClaimSubmissionEventAndSucc", &prevClaim). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) diff --git a/internal/claimer/side-effects.go b/internal/claimer/side-effects.go index 01645015a..0a2bd2e72 100644 --- a/internal/claimer/side-effects.go +++ b/internal/claimer/side-effects.go @@ -20,7 +20,12 @@ import ( type sideEffects interface { // database - selectClaimPairsPerApp() ( + selectSubmissionClaimPairsPerApp() ( + map[common.Address]*ClaimRow, + map[common.Address]*ClaimRow, + error, + ) + selectAcceptanceClaimPairsPerApp() ( map[common.Address]*ClaimRow, map[common.Address]*ClaimRow, error, @@ -29,6 +34,10 @@ type sideEffects interface { claim *ClaimRow, txHash common.Hash, ) error + updateEpochWithAcceptedClaim( + claim *ClaimRow, + txHash common.Hash, + ) error updateApplicationState( appID int64, state ApplicationState, @@ -45,6 +54,15 @@ type sideEffects interface { *iconsensus.IConsensusClaimSubmission, error, ) + findClaimAcceptanceEventAndSucc( + claim *ClaimRow, + endBlock *big.Int, + ) ( + *iconsensus.IConsensus, + *iconsensus.IConsensusClaimAcceptance, + *iconsensus.IConsensusClaimAcceptance, + error, + ) submitClaimToBlockchain( ic *iconsensus.IConsensus, claim *ClaimRow, @@ -62,23 +80,40 @@ type sideEffects interface { ) } -func (s *Service) selectClaimPairsPerApp() ( +func (s *Service) selectSubmissionClaimPairsPerApp() ( map[common.Address]*ClaimRow, map[common.Address]*ClaimRow, error, ) { - computed, accepted, err := s.repository.SelectClaimPairsPerApp(s.Context) + accepted, computed, err := s.repository.SelectSubmissionClaimPairsPerApp(s.Context) if err != nil { - s.Logger.Error("selectClaimPairsPerApp:failed", + s.Logger.Error("selectSubmissionClaimPairsPerApp:failed", "error", err) } else { - s.Logger.Debug("selectClaimPairsPerApp:success", + s.Logger.Debug("selectSubmissionClaimPairsPerApp:success", "len(computed)", len(computed), "len(accepted)", len(accepted)) } return accepted, computed, err } +func (s *Service) selectAcceptanceClaimPairsPerApp() ( + map[common.Address]*ClaimRow, + map[common.Address]*ClaimRow, + error, +) { + accepted, submitted, err := s.repository.SelectAcceptanceClaimPairsPerApp(s.Context) + if err != nil { + s.Logger.Error("selectAcceptanceClaimPairsPerApp:failed", + "error", err) + } else { + s.Logger.Debug("selectAcceptanceClaimPairsPerApp:success", + "len(submitted)", len(submitted), + "len(accepted)", len(accepted)) + } + return accepted, submitted, err +} + /* update the database epoch status to CLAIM_SUBMITTED and add a transaction hash */ func (s *Service) updateEpochWithSubmittedClaim( claim *ClaimRow, @@ -102,6 +137,29 @@ func (s *Service) updateEpochWithSubmittedClaim( return err } +/* update the database epoch status to CLAIM_SUBMITTED and add a transaction hash */ +func (s *Service) updateEpochWithAcceptedClaim( + claim *ClaimRow, + txHash common.Hash, +) error { + err := s.repository.UpdateEpochWithAcceptedClaim(s.Context, claim.ApplicationID, claim.Index) + if err != nil { + s.Logger.Error("updateEpochWithSubmittedClaim:failed", + "appContractAddress", claim.IApplicationAddress, + "hash", claim.ClaimHash, + "last_block", claim.LastBlock, + "txHash", txHash, + "error", err) + } else { + s.Logger.Debug("updateEpochWithSubmittedClaim:success", + "appContractAddress", claim.IApplicationAddress, + "last_block", claim.LastBlock, + "hash", claim.ClaimHash, + "txHash", txHash) + } + return err +} + func (s *Service) updateApplicationState( appID int64, state ApplicationState, @@ -135,7 +193,7 @@ func (s *Service) findClaimSubmissionEventAndSucc( ) { ic, curr, next, err := s.FindClaimSubmissionEventAndSucc(claim, endBlock) if err != nil { - s.Logger.Error("findClaimSubmissionEventAndSucc:failed", + s.Logger.Debug("findClaimSubmissionEventAndSucc:failed", "claim", claim, "error", err) } else { @@ -176,7 +234,7 @@ func (s *Service) submitClaimToBlockchain( func (s *Service) pollTransaction(txHash common.Hash, endBlock *big.Int) (bool, *types.Receipt, error) { ready, receipt, err := s.PollTransaction(txHash, endBlock) if err != nil { - s.Logger.Error("PollTransaction:failed", + s.Logger.Debug("PollTransaction:failed", "tx", txHash, "error", err) } else if ready { @@ -240,9 +298,9 @@ func (s *Service) FindClaimSubmissionEventAndSucc( it, err := ethutil.ChunkedFilterLogs(s.Context, s.ethConn, ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(claim.Epoch.LastBlock), - ToBlock: endBlock, + ToBlock: endBlock, Addresses: []common.Address{claim.IConsensusAddress}, - Topics: topics, + Topics: topics, }) if err != nil { return nil, nil, nil, err @@ -258,7 +316,7 @@ func (s *Service) FindClaimSubmissionEventAndSucc( } lastBlock := event.LastProcessedBlockNumber.Uint64() - if claimMatchesEvent(claim, event) { + if claimSubmissionMatch(claim, event) { // found the event, does it has a successor? try to fetch it succ, ok, err := unwrapClaimSubmission(ic, next) if !ok || err != nil { @@ -272,6 +330,108 @@ func (s *Service) FindClaimSubmissionEventAndSucc( } } +func unwrapClaimAcceptance( + ic *iconsensus.IConsensus, + pull func() (log *types.Log, err error, ok bool), +) ( + *iconsensus.IConsensusClaimAcceptance, + bool, + error, +) { + log, err, ok := pull() + if !ok || err != nil { + return nil, false, err + } + ev, err := ic.ParseClaimAcceptance(*log) + return ev, true, err +} + +func (s *Service) findClaimAcceptanceEventAndSucc( + claim *ClaimRow, + endBlock *big.Int, +) ( + *iconsensus.IConsensus, + *iconsensus.IConsensusClaimAcceptance, + *iconsensus.IConsensusClaimAcceptance, + error, +) { + ic, curr, next, err := s.FindClaimAcceptanceEventAndSucc(claim, endBlock) + if err != nil { + s.Logger.Debug("findClaimAcceptanceEventAndSucc:failed", + "claim", claim, + "error", err) + } else { + s.Logger.Debug("findClaimAcceptanceEventAndSucc:success", + "claim", claim, + "currEvent", curr, + "nextEvent", next, + ) + } + return ic, curr, next, err +} + +// scan the event stream for a claimAcceptance event that matches claim. +// return this event and its successor +func (s *Service) FindClaimAcceptanceEventAndSucc( + claim *ClaimRow, + endBlock *big.Int, +) ( + *iconsensus.IConsensus, + *iconsensus.IConsensusClaimAcceptance, + *iconsensus.IConsensusClaimAcceptance, + error, +) { + ic, err := iconsensus.NewIConsensus(claim.IConsensusAddress, s.ethConn) + if err != nil { + return nil, nil, nil, err + } + + // filter must match: + // - `ClaimAcceptance` events + // - appContract == claim.IApplicationAddress + c, err := iconsensus.IConsensusMetaData.GetAbi() + topics, err := abi.MakeTopics( + []interface{}{c.Events["ClaimAcceptance"].ID}, + []interface{}{claim.IApplicationAddress}, + ) + if err != nil { + return nil, nil, nil, err + } + + it, err := ethutil.ChunkedFilterLogs(s.Context, s.ethConn, ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(claim.Epoch.LastBlock), + ToBlock: endBlock, + Addresses: []common.Address{claim.IConsensusAddress}, + Topics: topics, + }) + if err != nil { + return nil, nil, nil, err + } + + // pull events instead of iterating + next, stop := iter.Pull2(it) + defer stop() + for { + event, ok, err := unwrapClaimAcceptance(ic, next) + if !ok || err != nil { + return ic, event, nil, err + } + lastBlock := event.LastProcessedBlockNumber.Uint64() + + if claimAcceptanceMatch(claim, event) { + // found the event, does it has a successor? try to fetch it + succ, ok, err := unwrapClaimAcceptance(ic, next) + if !ok || err != nil { + return ic, event, nil, err + } + return ic, event, succ, err + } else if lastBlock > claim.Epoch.LastBlock { + err = fmt.Errorf("No matching claim, searched up to %v", event) + return nil, nil, nil, err + } + } +} + /* poll a transaction hash for its submission status and receipt */ func (s *Service) PollTransaction(txHash common.Hash, endBlock *big.Int) (bool, *types.Receipt, error) { _, isPending, err := s.ethConn.TransactionByHash(s.Context, txHash) diff --git a/internal/evmreader/claim.go b/internal/evmreader/claim.go deleted file mode 100644 index 9a8c6081a..000000000 --- a/internal/evmreader/claim.go +++ /dev/null @@ -1,266 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package evmreader - -import ( - "cmp" - "context" - "strings" - - . "github.com/cartesi/rollups-node/internal/model" - "github.com/cartesi/rollups-node/internal/repository" - "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" -) - -func (r *Service) checkForClaimStatus( - ctx context.Context, - apps []appContracts, - mostRecentBlockNumber uint64, -) { - - r.Logger.Debug("Checking for new Claim Acceptance Events") - - // Classify them by lastClaimCheck block - appsIndexedByLastCheck := indexApps(keyByLastClaimCheck, apps) - - for lastClaimCheck, apps := range appsIndexedByLastCheck { - - appAddresses := appsToAddresses(apps) - - // Safeguard: Only check blocks starting from the block where the InputBox - // contract was deployed as Inputs can be added to that same block - if lastClaimCheck < r.inputBoxDeploymentBlock { - lastClaimCheck = r.inputBoxDeploymentBlock - 1 - } - - if mostRecentBlockNumber > lastClaimCheck { - - r.Logger.Debug("Checking claim acceptance for applications", - "apps", appAddresses, - "last claim check block", lastClaimCheck, - "most recent block", mostRecentBlockNumber) - - r.readAndUpdateClaims(ctx, apps, lastClaimCheck, mostRecentBlockNumber) - - } else if mostRecentBlockNumber < lastClaimCheck { - r.Logger.Warn( - "Not reading claim acceptance: most recent block is lower than the last processed one", //nolint:lll - "apps", appAddresses, - "last claim check block", lastClaimCheck, - "most recent block", mostRecentBlockNumber, - ) - } else { - r.Logger.Warn("Not reading claim acceptance: already checked the most recent blocks", - "apps", appAddresses, - "last claim check block", lastClaimCheck, - "most recent block", mostRecentBlockNumber, - ) - } - - } -} - -func getPreviousEpochsWithSubmittedClaims(ctx context.Context, er EvmReaderRepository, appAddress string, block uint64) ([]*Epoch, uint64, error) { - f := repository.EpochFilter{Status: Pointer(EpochStatus_ClaimSubmitted), BeforeBlock: Pointer(block)} - return er.ListEpochs(ctx, appAddress, f, repository.Pagination{}) -} - -func (r *Service) readAndUpdateClaims( - ctx context.Context, - apps []appContracts, - lastClaimCheck, mostRecentBlockNumber uint64, -) { - - // DISCLAIMER: The current algorithm will only handle Authority. - // To handle Quorum, node needs to handle acceptance events - // that can happen before claim submission - - // DISCLAIMER 2: The current algorithm does not consider that there might - // be more than one claimAcceptance per block - - // Classify them by the same IConsensusAddress - sameConsensusApps := indexApps(keyByIConsensus, apps) - for iConsensusAddress, apps := range sameConsensusApps { - - appAddresses := appsToAddresses(apps) - - // All apps shares the same IConsensus - // If there is a key on indexApps, there is at least one - // application in the referred application slice - consensusContract := apps[0].consensusContract - epochLength := apps[0].application.EpochLength - - // Retrieve Claim Acceptance Events from blockchain - appClaimAcceptanceEventMap, err := r.readClaimsAcceptance( - ctx, consensusContract, appAddresses, lastClaimCheck+1, mostRecentBlockNumber) - if err != nil { - r.Logger.Error("Error reading claim acceptance status", - "apps", apps, - "IConsensus", iConsensusAddress, - "start", lastClaimCheck, - "end", mostRecentBlockNumber, - "error", err) - continue - } - - // Check events against Epochs - APP_LOOP: - for app, claimAcceptances := range appClaimAcceptanceEventMap { - appHexAddress := strings.ToLower(app.Hex()) - for _, claimAcceptance := range claimAcceptances { - - // Get Previous Epochs with submitted claims, If is there any, - // Application is in an invalid State. - previousEpochs, _, err := getPreviousEpochsWithSubmittedClaims( - ctx, r.repository, appHexAddress, claimAcceptance.LastProcessedBlockNumber.Uint64()) - if err != nil { - r.Logger.Error("Error retrieving previous submitted claims", - "address", app, - "block", claimAcceptance.LastProcessedBlockNumber.Uint64(), - "error", err) - continue APP_LOOP - } - if len(previousEpochs) > 0 { - r.Logger.Error("Application got 'not accepted' claims. It is in an invalid state", - "claim last block", claimAcceptance.LastProcessedBlockNumber, - "app", app) - continue APP_LOOP - } - - // Get the Epoch for the current Claim Acceptance Event - epoch, err := r.repository.GetEpoch( - ctx, app.Hex(), calculateEpochIndex( - epochLength, - claimAcceptance.LastProcessedBlockNumber.Uint64()), - ) - if err != nil { - r.Logger.Error("Error retrieving Epoch", - "address", app, - "block", claimAcceptance.LastProcessedBlockNumber.Uint64(), - "error", err) - continue APP_LOOP - } - - // Check Epoch - if epoch == nil { - if r.inputReaderEnabled { - r.Logger.Error( - "Found claim acceptance event for an unknown epoch. Application is in an invalid state", //nolint:lll - "address", app, - "claim last block", claimAcceptance.LastProcessedBlockNumber, - "hash", claimAcceptance.Claim) - } else { - r.Logger.Warn( - "Found claim acceptance event for an epoch that does not exist on the database", - "address", app, - "claim last block", claimAcceptance.LastProcessedBlockNumber, - ) - - } - continue APP_LOOP - } - if epoch.ClaimHash == nil { - r.Logger.Warn( - "Found claim acceptance event, but claim hasn't been calculated yet", - "address", app, - "last_block", claimAcceptance.LastProcessedBlockNumber, - ) - continue APP_LOOP - } - if claimAcceptance.Claim != *epoch.ClaimHash || - claimAcceptance.LastProcessedBlockNumber.Uint64() != epoch.LastBlock { - r.Logger.Error("Accepted Claim does not match actual Claim. Application is in an invalid state", //nolint:lll - "address", app, - "last_block", epoch.LastBlock, - "hash", epoch.ClaimHash) - - continue APP_LOOP - } - if epoch.Status == EpochStatus_ClaimAccepted { - r.Logger.Debug("Claim already accepted. Skipping", - "address", app, - "last_block", claimAcceptance.LastProcessedBlockNumber.Uint64(), - "epoch_status", epoch.Status, - "hash", epoch.ClaimHash) - continue - } - if epoch.Status != EpochStatus_ClaimSubmitted { - // this happens when running on latest. EvmReader can see the event before - // the claim is marked as submitted by the claimer. - r.Logger.Debug("Epoch status is not submitted. Skipping for now", - "address", app, - "last_block", claimAcceptance.LastProcessedBlockNumber.Uint64(), - "epoch_status", epoch.Status, - "hash", epoch.ClaimHash) - continue APP_LOOP - } - - // Update Epoch claim status - r.Logger.Info("Claim Accepted", - "address", app, - "epoch_index", epoch.Index, - "last_block", epoch.LastBlock, - "hash", epoch.ClaimHash, - "last_claim_check_block", claimAcceptance.Raw.BlockNumber) - - epoch.Status = EpochStatus_ClaimAccepted - // Store epoch - err = r.repository.UpdateEpochsClaimAccepted( - ctx, appHexAddress, []*Epoch{epoch}, claimAcceptance.Raw.BlockNumber) - if err != nil { - r.Logger.Error("Error storing claims", "address", app, "error", err) - continue - } - } - - } - } -} - -func (r *Service) readClaimsAcceptance( - ctx context.Context, - consensusContract ConsensusContract, - appAddresses []common.Address, - startBlock, endBlock uint64, -) (map[common.Address][]*iconsensus.IConsensusClaimAcceptance, error) { - appClaimAcceptanceMap := make(map[common.Address][]*iconsensus.IConsensusClaimAcceptance) - for _, address := range appAddresses { - appClaimAcceptanceMap[address] = []*iconsensus.IConsensusClaimAcceptance{} - } - opts := &bind.FilterOpts{ - Context: ctx, - Start: startBlock, - End: &endBlock, - } - claimAcceptanceEvents, err := consensusContract.RetrieveClaimAcceptanceEvents( - opts, appAddresses) - if err != nil { - return nil, err - } - for _, event := range claimAcceptanceEvents { - appClaimAcceptanceMap[event.AppContract] = insertSorted( - sortByLastBlockNumber, appClaimAcceptanceMap[event.AppContract], event) - } - return appClaimAcceptanceMap, nil -} - -// keyByLastClaimCheck is a LastClaimCheck key extractor function intended -// to be used with `indexApps` function, see indexApps() -func keyByLastClaimCheck(app appContracts) uint64 { - return app.application.LastClaimCheckBlock -} - -// keyByIConsensus is a IConsensus address key extractor function intended -// to be used with `indexApps` function, see indexApps() -func keyByIConsensus(app appContracts) common.Address { - return app.application.IConsensusAddress -} - -// sortByLastBlockNumber is a ClaimAcceptance's by last block number sorting function. -// Intended to be used with insertSorted function, see insertSorted() -func sortByLastBlockNumber(a, b *iconsensus.IConsensusClaimAcceptance) int { - return cmp.Compare(a.LastProcessedBlockNumber.Uint64(), b.LastProcessedBlockNumber.Uint64()) -} diff --git a/internal/evmreader/claim_test.go b/internal/evmreader/claim_test.go deleted file mode 100644 index a51ab400b..000000000 --- a/internal/evmreader/claim_test.go +++ /dev/null @@ -1,740 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package evmreader - -import ( - "context" - "fmt" - "math/big" - "time" - - . "github.com/cartesi/rollups-node/internal/model" - "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" - "github.com/cartesi/rollups-node/pkg/contracts/iinputbox" - "github.com/cartesi/rollups-node/pkg/service" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/mock" -) - -func (s *EvmReaderSuite) TestNoClaimsAcceptance() { - wsClient := FakeWSEhtClient{} - s.evmReader.wsClient = &wsClient - s.evmReader.inputBoxDeploymentBlock = 0x10 - - // Prepare repository - s.repository.Unset("ListApplications") - s.repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E"), - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x10, - }}, uint64(1), nil).Once() - s.repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E"), - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x11, - }}, uint64(1), nil).Once() - - s.repository.Unset("UpdateEpochsClaimAccepted") - s.repository.On("UpdateEpochsClaimAccepted", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Once().Run(func(arguments mock.Arguments) { - obj := arguments.Get(1) - claims, ok := obj.([]*Epoch) - s.Require().True(ok) - s.Require().Equal(0, len(claims)) - - obj = arguments.Get(2) - lastClaimCheck, ok := obj.(uint64) - s.Require().True(ok) - s.Require().Equal(uint64(17), lastClaimCheck) - - }).Return(nil) - s.repository.On("UpdateEpochsClaimAccepted", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Once().Run(func(arguments mock.Arguments) { - obj := arguments.Get(1) - claims, ok := obj.([]*Epoch) - s.Require().True(ok) - s.Require().Equal(0, len(claims)) - - obj = arguments.Get(2) - lastClaimCheck, ok := obj.(uint64) - s.Require().True(ok) - s.Require().Equal(uint64(18), lastClaimCheck) - - }).Return(nil) - - //No Inputs - s.inputBox.Unset("RetrieveInputs") - s.inputBox.On("RetrieveInputs", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]iinputbox.IInputBoxInputAdded{}, nil) - - // Prepare Client - s.client.Unset("HeaderByNumber") - s.client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header0, nil).Once() - s.client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header1, nil).Once() - s.client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header2, nil).Once() - - // Start service - ready := make(chan struct{}, 1) - errChannel := make(chan error, 1) - - go func() { - errChannel <- s.evmReader.Run(s.ctx, ready) - }() - - select { - case <-ready: - break - case err := <-errChannel: - s.FailNow("unexpected error signal", err) - } - - wsClient.fireNewHead(&header0) - wsClient.fireNewHead(&header1) - time.Sleep(1 * time.Second) - - s.repository.AssertNumberOfCalls( - s.T(), - "UpdateEpochsClaimAccepted", - 0, - ) - -} - -func (s *EvmReaderSuite) TestReadClaimAcceptance() { - - appAddress := common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E") - - // Contract Factory - - consensusContract := &MockIConsensusContract{} - - contractFactory := newEmvReaderContractFactory() - - contractFactory.Unset("NewIConsensus") - contractFactory.On("NewIConsensus", - mock.Anything, - ).Return(consensusContract, nil) - - //New EVM Reader - wsClient := FakeWSEhtClient{} - s.evmReader.wsClient = &wsClient - s.evmReader.contractFactory = contractFactory - - // Prepare Claims Acceptance Events - - claimEvent0 := &iconsensus.IConsensusClaimAcceptance{ - AppContract: appAddress, - LastProcessedBlockNumber: big.NewInt(3), - Claim: common.HexToHash("0xdeadbeef"), - } - - claimEvents := []*iconsensus.IConsensusClaimAcceptance{claimEvent0} - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return(claimEvents, nil).Once() - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return([]*iconsensus.IConsensusClaimAcceptance{}, nil) - - // Epoch Length - consensusContract.On("GetEpochLength", - mock.Anything, - ).Return(big.NewInt(1), nil).Once() - - // Prepare repository - s.repository.Unset("ListApplications") - s.repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x10, - }}, uint64(1), nil).Once() - s.repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x11, - }}, uint64(1), nil).Once() - - claim1Hash := common.HexToHash("0xdeadbeef") - claim0 := &Epoch{ - Index: 3, - FirstBlock: 3, - LastBlock: 3, - Status: EpochStatus_ClaimSubmitted, - ClaimHash: &claim1Hash, - } - - s.repository.Unset("GetEpoch") - s.repository.On("GetEpoch", - mock.Anything, - mock.Anything, - mock.Anything).Return(claim0, nil) - - s.repository.Unset("ListEpochs") - s.repository.On("ListEpochs", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Epoch{}, uint64(0), nil) - - s.repository.Unset("UpdateEpochsClaimAccepted") - s.repository.On("UpdateEpochsClaimAccepted", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Once().Run(func(arguments mock.Arguments) { - obj := arguments.Get(2) - claims, ok := obj.([]*Epoch) - s.Require().True(ok) - s.Require().Equal(1, len(claims)) - claim0 := claims[0] - s.Require().Equal(uint64(3), claim0.LastBlock) - s.Require().Equal(EpochStatus_ClaimAccepted, claim0.Status) - - }).Return(nil) - - //No Inputs - s.inputBox.Unset("RetrieveInputs") - s.inputBox.On("RetrieveInputs", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]iinputbox.IInputBoxInputAdded{}, nil) - - // Prepare Client - s.client.Unset("HeaderByNumber") - s.client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header0, nil).Once() - - // Start service - ready := make(chan struct{}, 1) - errChannel := make(chan error, 1) - - go func() { - errChannel <- s.evmReader.Run(s.ctx, ready) - }() - - select { - case <-ready: - break - case err := <-errChannel: - s.FailNow("unexpected error signal", err) - } - - wsClient.fireNewHead(&header0) - time.Sleep(10 * time.Second) - - s.repository.AssertNumberOfCalls( - s.T(), - "UpdateEpochsClaimAccepted", - 1, - ) - -} - -func (s *EvmReaderSuite) TestCheckClaimFails() { - s.Run("whenRetrievePreviousEpochsFails", func() { - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - appAddress := common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E") - - // Contract Factory - - consensusContract := &MockIConsensusContract{} - - contractFactory := newEmvReaderContractFactory() - - contractFactory.Unset("NewIConsensus") - contractFactory.On("NewIConsensus", - mock.Anything, - ).Return(consensusContract, nil) - - //New EVM Reader - client := newMockEthClient() - inputBox := newMockInputBox() - repository := newMockRepository() - wsClient := &FakeWSEhtClient{} - s.evmReader.client = client - s.evmReader.wsClient = wsClient - s.evmReader.inputSource = inputBox - s.evmReader.repository = repository - - // Prepare Claims Acceptance Events - - claimEvent0 := &iconsensus.IConsensusClaimAcceptance{ - AppContract: appAddress, - LastProcessedBlockNumber: big.NewInt(3), - Claim: common.HexToHash("0xdeadbeef"), - } - - claimEvents := []*iconsensus.IConsensusClaimAcceptance{claimEvent0} - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return(claimEvents, nil).Once() - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return([]*iconsensus.IConsensusClaimAcceptance{}, nil) - - // Epoch Length - consensusContract.On("GetEpochLength", - mock.Anything, - ).Return(big.NewInt(1), nil).Once() - - // Prepare repository - repository.Unset("ListApplications") - repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x10, - }}, uint64(1), nil).Once() - repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x11, - }}, uint64(1), nil).Once() - - claim1Hash := common.HexToHash("0xdeadbeef") - claim1 := &Epoch{ - Index: 3, - FirstBlock: 3, - LastBlock: 3, - Status: EpochStatus_ClaimSubmitted, - ClaimHash: &claim1Hash, - } - - repository.Unset("GetEpoch") - repository.On("GetEpoch", - mock.Anything, - mock.Anything, - mock.Anything).Return(claim1, nil) - - repository.Unset("ListEpochs") - repository.On("ListEpochs", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Epoch{}, uint64(0), fmt.Errorf("No previous epochs for you")) - - repository.Unset("UpdateEpochsClaimAccepted") - repository.On("UpdateEpochsClaimAccepted", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return(nil) - - //No Inputs - inputBox.Unset("RetrieveInputs") - inputBox.On("RetrieveInputs", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]iinputbox.IInputBoxInputAdded{}, nil) - - // Prepare Client - client.Unset("HeaderByNumber") - client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header0, nil).Once() - - // Start service - ready := make(chan struct{}, 1) - errChannel := make(chan error, 1) - - go func() { - errChannel <- s.evmReader.Run(ctx, ready) - }() - - select { - case <-ready: - break - case err := <-errChannel: - s.FailNow("unexpected error signal", err) - } - - wsClient.fireNewHead(&header0) - time.Sleep(1 * time.Second) - - repository.AssertNumberOfCalls( - s.T(), - "UpdateEpochsClaimAccepted", - 0, - ) - - }) - - s.Run("whenGetEpochsFails", func() { - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - appAddress := common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E") - - // Contract Factory - - consensusContract := &MockIConsensusContract{} - - contractFactory := newEmvReaderContractFactory() - - contractFactory.Unset("NewIConsensus") - contractFactory.On("NewIConsensus", - mock.Anything, - ).Return(consensusContract, nil) - - //New EVM Reader - client := newMockEthClient() - wsClient := FakeWSEhtClient{} - inputBox := newMockInputBox() - repository := newMockRepository() - evmReader := Service{ - client: client, - wsClient: &wsClient, - inputSource: inputBox, - repository: repository, - inputBoxDeploymentBlock: 0x00, - defaultBlock: DefaultBlock_Latest, - contractFactory: contractFactory, - hasEnabledApps: true, - inputReaderEnabled: true, - } - serviceArgs := &service.CreateInfo{Name: "evm-reader", Impl: &evmReader} - err := service.Create(ctx, serviceArgs, &evmReader.Service) - s.Require().Nil(err) - - // Prepare Claims Acceptance Events - - claimEvent0 := &iconsensus.IConsensusClaimAcceptance{ - AppContract: appAddress, - LastProcessedBlockNumber: big.NewInt(3), - Claim: common.HexToHash("0xdeadbeef"), - } - - claimEvents := []*iconsensus.IConsensusClaimAcceptance{claimEvent0} - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return(claimEvents, nil).Once() - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return([]*iconsensus.IConsensusClaimAcceptance{}, nil) - - // Epoch Length - consensusContract.On("GetEpochLength", - mock.Anything, - ).Return(big.NewInt(1), nil).Once() - - // Prepare repository - repository.Unset("ListApplications") - repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x10, - }}, uint64(1), nil).Once() - repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x11, - }}, uint64(1), nil).Once() - - claim0Hash := common.HexToHash("0xdeadbeef") - claim0 := &Epoch{ - Index: 1, - FirstBlock: 1, - LastBlock: 1, - Status: EpochStatus_ClaimSubmitted, - ClaimHash: &claim0Hash, - } - - repository.Unset("GetEpoch") - repository.On("GetEpoch", - mock.Anything, - mock.Anything, - mock.Anything).Return(nil, fmt.Errorf("No epoch for you")) - - repository.Unset("ListEpochs") - repository.On("ListEpochs", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Epoch{claim0}, uint64(1), nil) - - repository.Unset("UpdateEpochsClaimAccepted") - repository.On("UpdateEpochsClaimAccepted", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return(nil) - - //No Inputs - inputBox.Unset("RetrieveInputs") - inputBox.On("RetrieveInputs", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]iinputbox.IInputBoxInputAdded{}, nil) - - // Prepare Client - client.Unset("HeaderByNumber") - client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header0, nil).Once() - - // Start service - ready := make(chan struct{}, 1) - errChannel := make(chan error, 1) - - go func() { - errChannel <- evmReader.Run(ctx, ready) - }() - - select { - case <-ready: - break - case err := <-errChannel: - s.FailNow("unexpected error signal", err) - } - - wsClient.fireNewHead(&header0) - time.Sleep(1 * time.Second) - - repository.AssertNumberOfCalls( - s.T(), - "UpdateEpochsClaimAccepted", - 0, - ) - - }) - - s.Run("whenHasPreviousOpenClaims", func() { - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - appAddress := common.HexToAddress("0x2E663fe9aE92275242406A185AA4fC8174339D3E") - - // Contract Factory - - consensusContract := &MockIConsensusContract{} - - contractFactory := newEmvReaderContractFactory() - - contractFactory.Unset("NewIConsensus") - contractFactory.On("NewIConsensus", - mock.Anything, - ).Return(consensusContract, nil) - - //New EVM Reader - client := newMockEthClient() - wsClient := FakeWSEhtClient{} - inputBox := newMockInputBox() - repository := newMockRepository() - s.evmReader.client = client - s.evmReader.wsClient = &wsClient - s.evmReader.inputSource = inputBox - s.evmReader.repository = repository - - // Prepare Claims Acceptance Events - - claimEvent0 := &iconsensus.IConsensusClaimAcceptance{ - AppContract: appAddress, - LastProcessedBlockNumber: big.NewInt(3), - Claim: common.HexToHash("0xdeadbeef"), - } - - claimEvents := []*iconsensus.IConsensusClaimAcceptance{claimEvent0} - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return(claimEvents, nil).Once() - consensusContract.On("RetrieveClaimAcceptanceEvents", - mock.Anything, - mock.Anything, - ).Return([]*iconsensus.IConsensusClaimAcceptance{}, nil) - - // Epoch Length - consensusContract.On("GetEpochLength", - mock.Anything, - ).Return(big.NewInt(1), nil).Once() - - // Prepare repository - repository.Unset("ListApplications") - repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x10, - }}, uint64(1), nil).Once() - repository.On( - "ListApplications", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Application{{ - IApplicationAddress: appAddress, - IConsensusAddress: common.HexToAddress("0xdeadbeef"), - EpochLength: 10, - LastClaimCheckBlock: 0x11, - }}, uint64(1), nil).Once() - - claim0Hash := common.HexToHash("0xdeadbeef") - claim0 := &Epoch{ - Index: 1, - FirstBlock: 1, - LastBlock: 1, - Status: EpochStatus_ClaimSubmitted, - ClaimHash: &claim0Hash, - } - - repository.Unset("ListEpochs") - repository.On("ListEpochs", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]*Epoch{claim0}, uint64(1), nil) - - repository.Unset("UpdateEpochsClaimAccepted") - repository.On("UpdateEpochsClaimAccepted", - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Return(nil) - - //No Inputs - inputBox.Unset("RetrieveInputs") - inputBox.On("RetrieveInputs", - mock.Anything, - mock.Anything, - mock.Anything, - ).Return([]iinputbox.IInputBoxInputAdded{}, nil) - - // Prepare Client - client.Unset("HeaderByNumber") - client.On( - "HeaderByNumber", - mock.Anything, - mock.Anything, - ).Return(&header0, nil).Once() - - // Start service - ready := make(chan struct{}, 1) - errChannel := make(chan error, 1) - - go func() { - errChannel <- s.evmReader.Run(ctx, ready) - }() - - select { - case <-ready: - break - case err := <-errChannel: - s.FailNow("unexpected error signal", err) - } - - wsClient.fireNewHead(&header0) - time.Sleep(1 * time.Second) - - repository.AssertNumberOfCalls( - s.T(), - "UpdateEpochsClaimAccepted", - 0, - ) - - }) -} diff --git a/internal/evmreader/evmreader.go b/internal/evmreader/evmreader.go index 922f570bb..c24528510 100644 --- a/internal/evmreader/evmreader.go +++ b/internal/evmreader/evmreader.go @@ -382,8 +382,6 @@ func (r *Service) watchForNewBlocks(ctx context.Context, ready chan<- struct{}) r.checkForNewInputs(ctx, apps, blockNumber) - r.checkForClaimStatus(ctx, apps, blockNumber) - r.checkForOutputExecution(ctx, apps, blockNumber) } diff --git a/internal/repository/postgres/claimer.go b/internal/repository/postgres/claimer.go index a3bb8847a..3ee1659ac 100644 --- a/internal/repository/postgres/claimer.go +++ b/internal/repository/postgres/claimer.go @@ -19,12 +19,19 @@ var ( ErrNoUpdate = fmt.Errorf("update did not take effect") ) -// Retrieve the computed claim of each application with the smallest index. +// Retrieve the claim of each application with the smallest index. // The query may return either 0 or 1 entries per application. -func (r *PostgresRepository) SelectOldestComputedClaimPerApp(ctx context.Context) ( +func (r *PostgresRepository) selectOldestClaimPerApp( + ctx context.Context, + epochStatus model.EpochStatus, +) ( map[common.Address]*model.ClaimRow, error, ) { + if (epochStatus != model.EpochStatus_ClaimSubmitted) && (epochStatus != model.EpochStatus_ClaimComputed) { + return nil, fmt.Errorf("Invalid epoch status: %v", epochStatus) + } + // NOTE(mpolitzer): DISTINCT ON is a postgres extension. To implement // this in SQLite there is an alternative using GROUP BY and HAVING // clauses instead. @@ -50,10 +57,7 @@ func (r *PostgresRepository) SelectOldestComputedClaimPerApp(ctx context.Context table.Epoch.ApplicationID.EQ(table.Application.ID), ), ). - WHERE( - table.Epoch.Status.EQ(postgres.NewEnumValue(model.EpochStatus_ClaimComputed.String())). - AND(table.Application.State.EQ(postgres.NewEnumValue(model.ApplicationState_Enabled.String()))), - ). + WHERE(table.Epoch.Status.EQ(postgres.NewEnumValue(epochStatus.String())).AND(table.Application.State.EQ(postgres.NewEnumValue(model.ApplicationState_Enabled.String())))). ORDER_BY( table.Epoch.ApplicationID, table.Epoch.Index.ASC(), @@ -92,10 +96,18 @@ func (r *PostgresRepository) SelectOldestComputedClaimPerApp(ctx context.Context } // Retrieve the newest accepted claim of each application -func (r *PostgresRepository) SelectNewestSubmittedOrAcceptedClaimPerApp(ctx context.Context) ( +func (r *PostgresRepository) selectNewestAcceptedClaimPerApp( + ctx context.Context, + includeSubmitted bool, +) ( map[common.Address]*model.ClaimRow, error, ) { + expr := table.Epoch.Status.EQ(postgres.NewEnumValue(model.EpochStatus_ClaimAccepted.String())) + if includeSubmitted { + expr = expr.OR(table.Epoch.Status.EQ(postgres.NewEnumValue(model.EpochStatus_ClaimSubmitted.String()))) + } + // NOTE(mpolitzer): DISTINCT ON is a postgres extension. To implement // this in SQLite there is an alternative using GROUP BY and HAVING // clauses instead. @@ -121,11 +133,7 @@ func (r *PostgresRepository) SelectNewestSubmittedOrAcceptedClaimPerApp(ctx cont table.Epoch.ApplicationID.EQ(table.Application.ID), ), ). - WHERE( - table.Epoch.Status.EQ(postgres.NewEnumValue(model.EpochStatus_ClaimSubmitted.String())). - OR(table.Epoch.Status.EQ(postgres.NewEnumValue(model.EpochStatus_ClaimAccepted.String()))). - AND(table.Application.State.EQ(postgres.NewEnumValue(model.ApplicationState_Enabled.String()))), - ). + WHERE(expr.AND(table.Application.State.EQ(postgres.NewEnumValue(model.ApplicationState_Enabled.String())))). ORDER_BY( table.Epoch.ApplicationID, table.Epoch.Index.DESC(), @@ -163,7 +171,34 @@ func (r *PostgresRepository) SelectNewestSubmittedOrAcceptedClaimPerApp(ctx cont return results, nil } -func (r *PostgresRepository) SelectClaimPairsPerApp(ctx context.Context) ( +func (r *PostgresRepository) SelectSubmissionClaimPairsPerApp(ctx context.Context) ( + map[common.Address]*model.ClaimRow, + map[common.Address]*model.ClaimRow, + error, +) { + tx, err := r.db.BeginTx(ctx, pgx.TxOptions{ + IsoLevel: pgx.RepeatableRead, + AccessMode: pgx.ReadOnly, + }) + if err != nil { + return nil, nil, err + } + defer tx.Commit(ctx) + + computed, err := r.selectOldestClaimPerApp(ctx, model.EpochStatus_ClaimComputed) + if err != nil { + return nil, nil, err + } + + acceptedOrSubmitted, err := r.selectNewestAcceptedClaimPerApp(ctx, true) + if err != nil { + return nil, nil, err + } + + return acceptedOrSubmitted, computed, err +} + +func (r *PostgresRepository) SelectAcceptanceClaimPairsPerApp(ctx context.Context) ( map[common.Address]*model.ClaimRow, map[common.Address]*model.ClaimRow, error, @@ -177,17 +212,17 @@ func (r *PostgresRepository) SelectClaimPairsPerApp(ctx context.Context) ( } defer tx.Commit(ctx) - computed, err := r.SelectOldestComputedClaimPerApp(ctx) + submitted, err := r.selectOldestClaimPerApp(ctx, model.EpochStatus_ClaimSubmitted) if err != nil { return nil, nil, err } - accepted, err := r.SelectNewestSubmittedOrAcceptedClaimPerApp(ctx) + accepted, err := r.selectNewestAcceptedClaimPerApp(ctx, false) if err != nil { return nil, nil, err } - return computed, accepted, err + return accepted, submitted, err } func (r *PostgresRepository) UpdateEpochWithSubmittedClaim( @@ -224,3 +259,35 @@ func (r *PostgresRepository) UpdateEpochWithSubmittedClaim( } return nil } + +func (r *PostgresRepository) UpdateEpochWithAcceptedClaim( + ctx context.Context, + application_id int64, + index uint64, +) error { + updStmt := table.Epoch. + UPDATE( + table.Epoch.Status, + ). + SET( + postgres.NewEnumValue(model.EpochStatus_ClaimAccepted.String()), + ). + FROM( + table.Application, + ). + WHERE( + table.Epoch.ApplicationID.EQ(postgres.Int64(application_id)). + AND(table.Epoch.Index.EQ(postgres.RawFloat(fmt.Sprintf("%d", index)))). + AND(table.Epoch.Status.EQ(postgres.NewEnumValue(model.EpochStatus_ClaimSubmitted.String()))), + ) + + sqlStr, args := updStmt.Sql() + cmd, err := r.db.Exec(ctx, sqlStr, args...) + if err != nil { + return err + } + if cmd.RowsAffected() == 0 { + return ErrNoUpdate + } + return nil +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index e8ce4a7bc..b65804e93 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -110,15 +110,12 @@ type NodeConfigRepository interface { // TODO: migrate ClaimRow -> Application + Epoch and use the other interfaces type ClaimerRepository interface { - SelectOldestComputedClaimPerApp(ctx context.Context) ( + SelectSubmissionClaimPairsPerApp(ctx context.Context) ( map[common.Address]*ClaimRow, - error, - ) - SelectNewestSubmittedOrAcceptedClaimPerApp(ctx context.Context) ( map[common.Address]*ClaimRow, error, ) - SelectClaimPairsPerApp(ctx context.Context) ( + SelectAcceptanceClaimPairsPerApp(ctx context.Context) ( map[common.Address]*ClaimRow, map[common.Address]*ClaimRow, error, @@ -129,6 +126,11 @@ type ClaimerRepository interface { index uint64, transaction_hash common.Hash, ) error + UpdateEpochWithAcceptedClaim( + ctx context.Context, + application_id int64, + index uint64, + ) error } type Repository interface { From b61b5135b6d4d721bd6f271245dffeddc4fc11e3 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Wed, 26 Feb 2025 10:13:34 -0300 Subject: [PATCH 5/9] feat(claimer): test claim re-submission --- internal/claimer/claimer_test.go | 501 ++++++++++++------------------- 1 file changed, 188 insertions(+), 313 deletions(-) diff --git a/internal/claimer/claimer_test.go b/internal/claimer/claimer_test.go index 3c9ca0cd1..ed536c3a9 100644 --- a/internal/claimer/claimer_test.go +++ b/internal/claimer/claimer_test.go @@ -4,6 +4,7 @@ package claimer import ( + "fmt" "log/slog" "math/big" "os" @@ -100,6 +101,64 @@ func newServiceMock() *serviceMock { } } +func makeAcceptedClaim(i uint64) *ClaimRow { + hash := common.HexToHash("0x01") + tx := common.HexToHash("0x02") + return &ClaimRow{ + IApplicationAddress: common.HexToAddress("0x01"), + IConsensusAddress: common.HexToAddress("0x02"), + Epoch: Epoch{ + Index: i, + FirstBlock: i * 10, + LastBlock: i*10 + 9, + ClaimHash: &hash, + ClaimTransactionHash: &tx, + Status: model.EpochStatus_ClaimAccepted, + }, + } +} + +func makeSubmittedClaim(i uint64) *ClaimRow { + hash := common.HexToHash("0x01") + tx := common.HexToHash("0x02") + return &ClaimRow{ + IApplicationAddress: common.HexToAddress("0x01"), + IConsensusAddress: common.HexToAddress("0x02"), + Epoch: Epoch{ + Index: i, + FirstBlock: i * 10, + LastBlock: i*10 + 9, + ClaimHash: &hash, + ClaimTransactionHash: &tx, + Status: model.EpochStatus_ClaimSubmitted, + }, + } +} + +func makeComputedClaim(i uint64) *ClaimRow { + hash := common.HexToHash("0x01") + return &ClaimRow{ + IApplicationAddress: common.HexToAddress("0x01"), + IConsensusAddress: common.HexToAddress("0x02"), + Epoch: Epoch{ + Index: i, + FirstBlock: i * 10, + LastBlock: i*10 + 9, + ClaimHash: &hash, + ClaimTransactionHash: nil, + Status: model.EpochStatus_ClaimComputed, + }, + } +} + +func makeMatchingEvent(c *ClaimRow) *iconsensus.IConsensusClaimSubmission { + return &iconsensus.IConsensusClaimSubmission{ + LastProcessedBlockNumber: new(big.Int).SetUint64(c.LastBlock), + AppContract: c.IApplicationAddress, + Claim: *c.ClaimHash, + } +} + // ////////////////////////////////////////////////////////////////////////////// // Success // ////////////////////////////////////////////////////////////////////////////// @@ -118,34 +177,22 @@ func TestDoNothing(t *testing.T) { func TestSubmitFirstClaim(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") - claimTransactionHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } + currClaim := makeComputedClaim(3) var prevEvent *iconsensus.IConsensusClaimSubmission = nil var currEvent *iconsensus.IConsensusClaimSubmission = nil + prevClaims := map[common.Address]*ClaimRow{} currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). + m.On("findClaimSubmissionEventAndSucc", currClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) - m.On("submitClaimToBlockchain", nil, &currClaim). - Return(claimTransactionHash, nil) + m.On("submitClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) @@ -160,54 +207,25 @@ func TestSubmitFirstClaim(t *testing.T) { func TestSubmitClaimWithAntecessor(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") - claimTransactionHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") - prevClaimHash := common.HexToHash("0x101") - prevClaimTxHash := common.HexToHash("0x102") - prevClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &prevClaimHash, - ClaimTransactionHash: &prevClaimTxHash, - Status: model.EpochStatus_ClaimAccepted, - }, - } - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - prevClaims := map[common.Address]*ClaimRow{ - appContractAddress: &prevClaim, - } + prevClaim := makeAcceptedClaim(1) + currClaim := makeComputedClaim(3) var currEvent *iconsensus.IConsensusClaimSubmission = nil - prevEvent := &iconsensus.IConsensusClaimSubmission{ - LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock), - AppContract: appContractAddress, - Claim: *prevClaim.ClaimHash, + prevEvent := makeMatchingEvent(prevClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, } currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) - m.On("submitClaimToBlockchain", nil, &currClaim). - Return(claimTransactionHash, nil) + m.On("submitClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) @@ -223,34 +241,22 @@ func TestSkipSubmitFirstClaim(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) m.submissionEnabled = false - appContractAddress := common.HexToAddress("0x01") - claimTransactionHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } + currClaim := makeComputedClaim(3) var prevEvent *iconsensus.IConsensusClaimSubmission = nil var currEvent *iconsensus.IConsensusClaimSubmission = nil + prevClaims := map[common.Address]*ClaimRow{} currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). + m.On("findClaimSubmissionEventAndSucc", currClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) - m.On("submitClaimToBlockchain", nil, &currClaim). - Return(claimTransactionHash, nil) + m.On("submitClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) @@ -266,54 +272,25 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) m.submissionEnabled = false - appContractAddress := common.HexToAddress("0x01") - claimTransactionHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") - prevClaimHash := common.HexToHash("0x101") - prevClaimTxHash := common.HexToHash("0x102") - prevClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &prevClaimHash, - ClaimTransactionHash: &prevClaimTxHash, - Status: model.EpochStatus_ClaimAccepted, - }, - } - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - prevClaims := map[common.Address]*ClaimRow{ - appContractAddress: &prevClaim, - } + prevClaim := makeAcceptedClaim(1) + currClaim := makeComputedClaim(3) + prevEvent := makeMatchingEvent(prevClaim) var currEvent *iconsensus.IConsensusClaimSubmission = nil - prevEvent := &iconsensus.IConsensusClaimSubmission{ - LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock), - AppContract: appContractAddress, - Claim: *prevClaim.ClaimHash, + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, } currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) - m.On("submitClaimToBlockchain", nil, &currClaim). - Return(claimTransactionHash, nil) + m.On("submitClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) @@ -328,35 +305,24 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { func TestInFlightCompleted(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") reqHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") txHash := common.HexToHash("0x1000") - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } + + currClaim := makeComputedClaim(3) prevClaims := map[common.Address]*ClaimRow{} currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } - m.claimsInFlight[appContractAddress] = reqHash + m.claimsInFlight[currClaim.IApplicationAddress] = reqHash m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) m.On("pollTransaction", reqHash, endBlock). Return(true, &types.Receipt{ - ContractAddress: appContractAddress, + ContractAddress: currClaim.IApplicationAddress, TxHash: txHash, }, nil) - m.On("updateEpochWithSubmittedClaim", &currClaim, txHash). + m.On("updateEpochWithSubmittedClaim", currClaim, txHash). Return(nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) @@ -372,35 +338,20 @@ func TestInFlightCompleted(t *testing.T) { func TestUpdateFirstClaim(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") - claimHash := common.HexToHash("0x100") - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - var nilEvent *iconsensus.IConsensusClaimSubmission = nil - currEvent := iconsensus.IConsensusClaimSubmission{ - AppContract: appContractAddress, - LastProcessedBlockNumber: new(big.Int).SetUint64(currClaim.LastBlock), - Claim: *currClaim.ClaimHash, - } + currClaim := makeComputedClaim(3) + var prevEvent *iconsensus.IConsensusClaimSubmission = nil + currEvent := makeMatchingEvent(currClaim) + prevClaims := map[common.Address]*ClaimRow{} currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &currClaim, endBlock). - Return(&iconsensus.IConsensus{}, &currEvent, nilEvent, nil) - m.On("updateEpochWithSubmittedClaim", &currClaim, currEvent.Raw.TxHash). + m.On("findClaimSubmissionEventAndSucc", currClaim, endBlock). + Return(&iconsensus.IConsensus{}, currEvent, prevEvent, nil) + m.On("updateEpochWithSubmittedClaim", currClaim, currEvent.Raw.TxHash). Return(nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) @@ -416,55 +367,23 @@ func TestUpdateFirstClaim(t *testing.T) { func TestUpdateClaimWithAntecessor(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") - claimHash := common.HexToHash("0x100") - prevClaimHash := common.HexToHash("0x101") - prevClaimTxHash := common.HexToHash("0x102") - prevClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &prevClaimHash, - ClaimTransactionHash: &prevClaimTxHash, - Status: model.EpochStatus_ClaimAccepted, - }, - } - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - prevEvent := iconsensus.IConsensusClaimSubmission{ - AppContract: appContractAddress, - LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock), - Claim: *prevClaim.ClaimHash, - } - currEvent := iconsensus.IConsensusClaimSubmission{ - AppContract: appContractAddress, - LastProcessedBlockNumber: new(big.Int).SetUint64(currClaim.LastBlock), - Claim: *currClaim.ClaimHash, - } + prevClaim := makeAcceptedClaim(1) + currClaim := makeComputedClaim(3) + prevEvent := makeMatchingEvent(prevClaim) + currEvent := makeMatchingEvent(currClaim) + prevClaims := map[common.Address]*ClaimRow{ - appContractAddress: &prevClaim, + prevClaim.IApplicationAddress: prevClaim, } currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). - Return(&iconsensus.IConsensus{}, &prevEvent, &currEvent, nil) - m.On("updateEpochWithSubmittedClaim", &currClaim, currEvent.Raw.TxHash). + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + m.On("updateEpochWithSubmittedClaim", currClaim, currEvent.Raw.TxHash). Return(nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) @@ -481,55 +400,72 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { // Failure // ////////////////////////////////////////////////////////////////////////////// +// submit again after pollTransaction failure +func TestSubmitFailedClaim(t *testing.T) { + m := newServiceMock() + + expectedErr := fmt.Errorf("not found") + endBlock := big.NewInt(0) + reqHash := common.HexToHash("0x01") + var nilReceipt *types.Receipt + + prevClaim := makeAcceptedClaim(1) + currClaim := makeComputedClaim(3) + prevEvent := makeMatchingEvent(prevClaim) + var currEvent *iconsensus.IConsensusClaimSubmission = nil + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + m.claimsInFlight[currClaim.IApplicationAddress] = reqHash + + m.On("selectSubmissionClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("pollTransaction", reqHash, endBlock). + Return(false, nilReceipt, expectedErr).Once() + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() + m.On("submitClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil).Once() + + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 0, len(errs)) + + // submission failed and got retried + m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) + m.AssertNumberOfCalls(t, "pollTransaction", 1) + m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) + m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 1) + m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) +} + // !claimMatchesEvent(prevClaim, prevEvent) func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") + prevClaim := makeAcceptedClaim(1) + currClaim := makeComputedClaim(3) claimTransactionHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") - prevClaimHash := common.HexToHash("0x101") - prevClaimTxHash := common.HexToHash("0x102") - prevClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &prevClaimHash, - ClaimTransactionHash: &prevClaimTxHash, - Status: model.EpochStatus_ClaimAccepted, - }, - } - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - prevClaims := map[common.Address]*ClaimRow{ - appContractAddress: &prevClaim, - } - var currEvent *iconsensus.IConsensusClaimSubmission = nil prevEvent := &iconsensus.IConsensusClaimSubmission{ LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock + 1), - AppContract: appContractAddress, + AppContract: prevClaim.IApplicationAddress, Claim: *prevClaim.ClaimHash, } + var currEvent *iconsensus.IConsensusClaimSubmission = nil + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) m.On("updateApplicationState", int64(0), model.ApplicationState_Inoperable, mock.Anything). Return(nil) @@ -545,58 +481,26 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { func TestSubmitClaimWithEventMismatch(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) - appContractAddress := common.HexToAddress("0x01") - claimHash := common.HexToHash("0x100") - prevClaimHash := common.HexToHash("0x101") - prevClaimTxHash := common.HexToHash("0x102") - prevClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &prevClaimHash, - ClaimTransactionHash: &prevClaimTxHash, - Status: model.EpochStatus_ClaimAccepted, - }, - } - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 3, - FirstBlock: 30, - LastBlock: 39, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - prevEvent := iconsensus.IConsensusClaimSubmission{ - AppContract: appContractAddress, - LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock), - Claim: *prevClaim.ClaimHash, - } - currEvent := iconsensus.IConsensusClaimSubmission{ - AppContract: appContractAddress, - LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock + 1), - Claim: *currClaim.ClaimHash, - } + prevClaim := makeAcceptedClaim(1) + wrongClaim := makeComputedClaim(2) + currClaim := makeComputedClaim(3) + wrongEvent := makeMatchingEvent(wrongClaim) + prevEvent := makeMatchingEvent(prevClaim) + prevClaims := map[common.Address]*ClaimRow{ - appContractAddress: &prevClaim, + prevClaim.IApplicationAddress: prevClaim, } currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } + m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim, endBlock). - Return(&iconsensus.IConsensus{}, &prevEvent, &currEvent, nil) + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, wrongEvent, nil) m.On("updateApplicationState", int64(0), model.ApplicationState_Inoperable, mock.Anything). Return(nil) - m.On("updateEpochWithSubmittedClaim", &currClaim, currEvent.Raw.TxHash). - Return(nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 1) @@ -606,56 +510,27 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { // !checkClaimsConstraint(prevClaim, currClaim) func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { m := newServiceMock() - appContractAddress := common.HexToAddress("0x01") - claimTransactionHash := common.HexToHash("0x10") - claimHash := common.HexToHash("0x100") - prevClaimHash := common.HexToHash("0x101") - prevClaimTxHash := common.HexToHash("0x102") - prevClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 2, - FirstBlock: 20, - LastBlock: 29, - ClaimHash: &prevClaimHash, - ClaimTransactionHash: &prevClaimTxHash, - Status: model.EpochStatus_ClaimAccepted, - }, - } - currClaim := ClaimRow{ - IApplicationAddress: appContractAddress, - IConsensusAddress: appContractAddress, - Epoch: Epoch{ - Index: 1, - FirstBlock: 10, - LastBlock: 19, - ClaimHash: &claimHash, - Status: model.EpochStatus_ClaimComputed, - }, - } - prevClaims := map[common.Address]*ClaimRow{ - appContractAddress: &prevClaim, - } + wrongClaim := makeComputedClaim(2) + wrongEvent := makeMatchingEvent(wrongClaim) + currClaim := makeComputedClaim(1) var currEvent *iconsensus.IConsensusClaimSubmission = nil - prevEvent := &iconsensus.IConsensusClaimSubmission{ - LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock + 1), - AppContract: appContractAddress, - Claim: *prevClaim.ClaimHash, + + prevClaims := map[common.Address]*ClaimRow{ + wrongClaim.IApplicationAddress: wrongClaim, } currClaims := map[common.Address]*ClaimRow{ - appContractAddress: &currClaim, + currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). Return(prevClaims, currClaims, nil) - m.On("findClaimSubmissionEventAndSucc", &prevClaim). - Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + m.On("findClaimSubmissionEventAndSucc", wrongClaim). + Return(&iconsensus.IConsensus{}, wrongEvent, currEvent, nil) + m.On("submitClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) m.On("updateApplicationState", int64(0), model.ApplicationState_Inoperable, mock.Anything). Return(nil) - m.On("submitClaimToBlockchain", nil, &currClaim). - Return(claimTransactionHash, nil) errs := m.submitClaimsAndUpdateDatabase(m, big.NewInt(0)) assert.Equal(t, len(errs), 1) From 64a82b8c8d83e611d304fd0a403b8a2e29344c88 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Thu, 27 Feb 2025 09:49:11 -0300 Subject: [PATCH 6/9] feat(claimer): improve test coverage --- internal/claimer/claimer_test.go | 485 ++++++++++++++++++++++++++----- 1 file changed, 415 insertions(+), 70 deletions(-) diff --git a/internal/claimer/claimer_test.go b/internal/claimer/claimer_test.go index ed536c3a9..8ce84b7fc 100644 --- a/internal/claimer/claimer_test.go +++ b/internal/claimer/claimer_test.go @@ -38,6 +38,16 @@ func (m *serviceMock) selectSubmissionClaimPairsPerApp() ( args.Get(1).(map[common.Address]*ClaimRow), args.Error(2) } +func (m *serviceMock) selectAcceptanceClaimPairsPerApp() ( + map[common.Address]*ClaimRow, + map[common.Address]*ClaimRow, + error, +) { + args := m.Called() + return args.Get(0).(map[common.Address]*ClaimRow), + args.Get(1).(map[common.Address]*ClaimRow), + args.Error(2) +} func (m *serviceMock) updateEpochWithSubmittedClaim( claim *ClaimRow, txHash common.Hash, @@ -51,6 +61,14 @@ func (m *serviceMock) updateApplicationState(appID int64, state ApplicationState return args.Error(0) } +func (m *serviceMock) updateEpochWithAcceptedClaim( + claim *ClaimRow, + txHash common.Hash, +) error { + args := m.Called(claim, txHash) + return args.Error(0) +} + func (m *serviceMock) findClaimSubmissionEventAndSucc( claim *ClaimRow, endBlock *big.Int, @@ -66,6 +84,21 @@ func (m *serviceMock) findClaimSubmissionEventAndSucc( args.Get(2).(*iconsensus.IConsensusClaimSubmission), args.Error(3) } +func (m *serviceMock) findClaimAcceptanceEventAndSucc( + claim *ClaimRow, + endBlock *big.Int, +) ( + *iconsensus.IConsensus, + *iconsensus.IConsensusClaimAcceptance, + *iconsensus.IConsensusClaimAcceptance, + error, +) { + args := m.Called(claim, endBlock) + return args.Get(0).(*iconsensus.IConsensus), + args.Get(1).(*iconsensus.IConsensusClaimAcceptance), + args.Get(2).(*iconsensus.IConsensusClaimAcceptance), + args.Error(3) +} func (m *serviceMock) submitClaimToBlockchain( instance *iconsensus.IConsensus, @@ -151,11 +184,25 @@ func makeComputedClaim(i uint64) *ClaimRow { } } -func makeMatchingEvent(c *ClaimRow) *iconsensus.IConsensusClaimSubmission { +func makeSubmissionEvent(c *ClaimRow) *iconsensus.IConsensusClaimSubmission { return &iconsensus.IConsensusClaimSubmission{ LastProcessedBlockNumber: new(big.Int).SetUint64(c.LastBlock), AppContract: c.IApplicationAddress, Claim: *c.ClaimHash, + Raw: types.Log{ + TxHash: common.HexToHash("0x01"), + }, + } +} + +func makeAcceptanceEvent(c *ClaimRow) *iconsensus.IConsensusClaimAcceptance { + return &iconsensus.IConsensusClaimAcceptance{ + LastProcessedBlockNumber: new(big.Int).SetUint64(c.LastBlock), + AppContract: c.IApplicationAddress, + Claim: *c.ClaimHash, + Raw: types.Log{ + TxHash: common.HexToHash("0x01"), + }, } } @@ -188,20 +235,15 @@ func TestSubmitFirstClaim(t *testing.T) { } m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("findClaimSubmissionEventAndSucc", currClaim, endBlock). - Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() m.On("submitClaimToBlockchain", nil, currClaim). - Return(common.HexToHash("0x10"), nil) + Return(common.HexToHash("0x10"), nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 1) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) - m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 1) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } func TestSubmitClaimWithAntecessor(t *testing.T) { @@ -211,7 +253,7 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { prevClaim := makeAcceptedClaim(1) currClaim := makeComputedClaim(3) var currEvent *iconsensus.IConsensusClaimSubmission = nil - prevEvent := makeMatchingEvent(prevClaim) + prevEvent := makeSubmissionEvent(prevClaim) prevClaims := map[common.Address]*ClaimRow{ prevClaim.IApplicationAddress: prevClaim, @@ -221,20 +263,15 @@ func TestSubmitClaimWithAntecessor(t *testing.T) { } m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). - Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() m.On("submitClaimToBlockchain", nil, currClaim). - Return(common.HexToHash("0x10"), nil) + Return(common.HexToHash("0x10"), nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 1) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) - m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 1) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } func TestSkipSubmitFirstClaim(t *testing.T) { @@ -252,20 +289,15 @@ func TestSkipSubmitFirstClaim(t *testing.T) { } m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("findClaimSubmissionEventAndSucc", currClaim, endBlock). - Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() m.On("submitClaimToBlockchain", nil, currClaim). - Return(common.HexToHash("0x10"), nil) + Return(common.HexToHash("0x10"), nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) - m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } func TestSkipSubmitClaimWithAntecessor(t *testing.T) { @@ -275,7 +307,7 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { prevClaim := makeAcceptedClaim(1) currClaim := makeComputedClaim(3) - prevEvent := makeMatchingEvent(prevClaim) + prevEvent := makeSubmissionEvent(prevClaim) var currEvent *iconsensus.IConsensusClaimSubmission = nil prevClaims := map[common.Address]*ClaimRow{ @@ -286,20 +318,15 @@ func TestSkipSubmitClaimWithAntecessor(t *testing.T) { } m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). - Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() m.On("submitClaimToBlockchain", nil, currClaim). - Return(common.HexToHash("0x10"), nil) + Return(common.HexToHash("0x10"), nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) - m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } func TestInFlightCompleted(t *testing.T) { @@ -316,23 +343,18 @@ func TestInFlightCompleted(t *testing.T) { m.claimsInFlight[currClaim.IApplicationAddress] = reqHash m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("pollTransaction", reqHash, endBlock). Return(true, &types.Receipt{ ContractAddress: currClaim.IApplicationAddress, TxHash: txHash, - }, nil) + }, nil).Once() m.On("updateEpochWithSubmittedClaim", currClaim, txHash). - Return(nil) + Return(nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 0) - m.AssertNumberOfCalls(t, "pollTransaction", 1) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 1) } func TestUpdateFirstClaim(t *testing.T) { @@ -341,27 +363,22 @@ func TestUpdateFirstClaim(t *testing.T) { currClaim := makeComputedClaim(3) var prevEvent *iconsensus.IConsensusClaimSubmission = nil - currEvent := makeMatchingEvent(currClaim) + currEvent := makeSubmissionEvent(currClaim) prevClaims := map[common.Address]*ClaimRow{} currClaims := map[common.Address]*ClaimRow{ currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("findClaimSubmissionEventAndSucc", currClaim, endBlock). - Return(&iconsensus.IConsensus{}, currEvent, prevEvent, nil) + Return(&iconsensus.IConsensus{}, currEvent, prevEvent, nil).Once() m.On("updateEpochWithSubmittedClaim", currClaim, currEvent.Raw.TxHash). - Return(nil) + Return(nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) - m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 1) } func TestUpdateClaimWithAntecessor(t *testing.T) { @@ -370,8 +387,8 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { prevClaim := makeAcceptedClaim(1) currClaim := makeComputedClaim(3) - prevEvent := makeMatchingEvent(prevClaim) - currEvent := makeMatchingEvent(currClaim) + prevEvent := makeSubmissionEvent(prevClaim) + currEvent := makeSubmissionEvent(currClaim) prevClaims := map[common.Address]*ClaimRow{ prevClaim.IApplicationAddress: prevClaim, @@ -380,26 +397,109 @@ func TestUpdateClaimWithAntecessor(t *testing.T) { currClaim.IApplicationAddress: currClaim, } m.On("selectSubmissionClaimPairsPerApp"). - Return(prevClaims, currClaims, nil) + Return(prevClaims, currClaims, nil).Once() m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). - Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() m.On("updateEpochWithSubmittedClaim", currClaim, currEvent.Raw.TxHash). - Return(nil) + Return(nil).Once() errs := m.submitClaimsAndUpdateDatabase(m, endBlock) assert.Equal(t, len(errs), 0) assert.Equal(t, len(m.claimsInFlight), 0) - m.AssertNumberOfCalls(t, "findClaimSubmissionEventAndSucc", 1) - m.AssertNumberOfCalls(t, "pollTransaction", 0) - m.AssertNumberOfCalls(t, "selectSubmissionClaimPairsPerApp", 1) - m.AssertNumberOfCalls(t, "submitClaimToBlockchain", 0) - m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 1) +} + +func TestAcceptFirstClaim(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + + currClaim := makeSubmittedClaim(3) + var prevEvent *iconsensus.IConsensusClaimAcceptance = nil + currEvent := makeAcceptanceEvent(currClaim) + + prevClaims := map[common.Address]*ClaimRow{} + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("findClaimAcceptanceEventAndSucc", currClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, len(errs), 0) +} + +func TestAcceptClaimWithAntecessor(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + + prevClaim := makeAcceptedClaim(1) + currClaim := makeSubmittedClaim(3) + prevEvent := makeAcceptanceEvent(prevClaim) + currEvent := makeAcceptanceEvent(currClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp").Return(prevClaims, currClaims, nil).Once() + m.On("findClaimAcceptanceEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() + m.On("updateEpochWithAcceptedClaim", currClaim, currEvent.Raw.TxHash). + Return(nil).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, len(errs), 0) } // ////////////////////////////////////////////////////////////////////////////// // Failure // ////////////////////////////////////////////////////////////////////////////// +// try again later on failure to fetch claims +func TestDatabaseSelectFailure(t *testing.T) { + m := newServiceMock() + + expectedErr := fmt.Errorf("not found") + endBlock := big.NewInt(0) + + prevClaims := map[common.Address]*ClaimRow{} + currClaims := map[common.Address]*ClaimRow{} + + m.On("selectSubmissionClaimPairsPerApp"). + Return(prevClaims, currClaims, expectedErr).Once() + + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, errs[0], expectedErr) +} + +func TestClaimInFlightMissingFromCurrClaims(t *testing.T) { + m := newServiceMock() + + endBlock := big.NewInt(0) + reqHash := common.HexToHash("0x01") + receipt := new(types.Receipt) + + currClaim := makeComputedClaim(3) + + prevClaims := map[common.Address]*ClaimRow{} + currClaims := map[common.Address]*ClaimRow{} + m.claimsInFlight[currClaim.IApplicationAddress] = reqHash + + m.On("selectSubmissionClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("pollTransaction", reqHash, endBlock). + Return(true, receipt, nil).Once() + + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, len(errs), 0) +} + // submit again after pollTransaction failure func TestSubmitFailedClaim(t *testing.T) { m := newServiceMock() @@ -411,7 +511,7 @@ func TestSubmitFailedClaim(t *testing.T) { prevClaim := makeAcceptedClaim(1) currClaim := makeComputedClaim(3) - prevEvent := makeMatchingEvent(prevClaim) + prevEvent := makeSubmissionEvent(prevClaim) var currEvent *iconsensus.IConsensusClaimSubmission = nil prevClaims := map[common.Address]*ClaimRow{ @@ -442,7 +542,7 @@ func TestSubmitFailedClaim(t *testing.T) { m.AssertNumberOfCalls(t, "updateEpochWithSubmittedClaim", 0) } -// !claimMatchesEvent(prevClaim, prevEvent) +// !claimSubmissionMatche(prevClaim, prevEvent) func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { m := newServiceMock() endBlock := big.NewInt(0) @@ -473,7 +573,7 @@ func TestSubmitClaimWithAntecessorMismatch(t *testing.T) { Return(claimTransactionHash, nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) - assert.Equal(t, len(errs), 1) + assert.Equal(t, 1, len(errs)) assert.Equal(t, errs[0], ErrEventMismatch) } @@ -485,8 +585,8 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { prevClaim := makeAcceptedClaim(1) wrongClaim := makeComputedClaim(2) currClaim := makeComputedClaim(3) - wrongEvent := makeMatchingEvent(wrongClaim) - prevEvent := makeMatchingEvent(prevClaim) + wrongEvent := makeSubmissionEvent(wrongClaim) + prevEvent := makeSubmissionEvent(prevClaim) prevClaims := map[common.Address]*ClaimRow{ prevClaim.IApplicationAddress: prevClaim, @@ -503,7 +603,7 @@ func TestSubmitClaimWithEventMismatch(t *testing.T) { Return(nil) errs := m.submitClaimsAndUpdateDatabase(m, endBlock) - assert.Equal(t, len(errs), 1) + assert.Equal(t, 1, len(errs)) assert.Equal(t, errs[0], ErrEventMismatch) } @@ -512,7 +612,7 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { m := newServiceMock() wrongClaim := makeComputedClaim(2) - wrongEvent := makeMatchingEvent(wrongClaim) + wrongEvent := makeSubmissionEvent(wrongClaim) currClaim := makeComputedClaim(1) var currEvent *iconsensus.IConsensusClaimSubmission = nil @@ -533,6 +633,251 @@ func TestSubmitClaimWithAntecessorOutOfOrder(t *testing.T) { Return(nil) errs := m.submitClaimsAndUpdateDatabase(m, big.NewInt(0)) - assert.Equal(t, len(errs), 1) + assert.Equal(t, 1, len(errs)) assert.Equal(t, errs[0], ErrClaimMismatch) } + +func TestErrSubmissionMissingEvent(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + + prevClaim := makeComputedClaim(1) + var prevEvent *iconsensus.IConsensusClaimSubmission = nil + currClaim := makeComputedClaim(2) + currEvent := makeSubmissionEvent(currClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectSubmissionClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("findClaimSubmissionEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() + m.On("updateApplicationState", int64(0), model.ApplicationState_Inoperable, mock.Anything). + Return(nil) + + errs := m.submitClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, errs[0], ErrMissingEvent) +} + +//////////////////////////////////////////////////////////////////////////////// + +func TestDatabaseAcceptanceSelectFailure(t *testing.T) { + m := newServiceMock() + + expectedErr := fmt.Errorf("not found") + endBlock := big.NewInt(0) + + prevClaims := map[common.Address]*ClaimRow{} + currClaims := map[common.Address]*ClaimRow{} + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, expectedErr).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, errs[0], expectedErr) +} + +func TestFindClaimAcceptanceEventAndSuccFailure0(t *testing.T) { + m := newServiceMock() + + expectedErr := fmt.Errorf("not found") + endBlock := big.NewInt(0) + + currClaim := makeComputedClaim(2) + currEvent := makeAcceptanceEvent(currClaim) + var prevEvent *iconsensus.IConsensusClaimAcceptance = nil + + prevClaims := map[common.Address]*ClaimRow{} + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("findClaimAcceptanceEventAndSucc", currClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, expectedErr).Once() + m.On("AcceptClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, errs[0], expectedErr) +} + +func TestFindClaimAcceptanceEventAndSuccFailure1(t *testing.T) { + m := newServiceMock() + + expectedErr := fmt.Errorf("not found") + endBlock := big.NewInt(0) + + prevClaim := makeComputedClaim(1) + prevEvent := makeAcceptanceEvent(prevClaim) + currClaim := makeComputedClaim(2) + currEvent := makeAcceptanceEvent(currClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("findClaimAcceptanceEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, expectedErr).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, errs[0], expectedErr) +} + +// !claimAcceptanceMatch(prevClaim, prevEvent) +func TestAcceptClaimWithAntecessorMismatch(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + prevClaim := makeAcceptedClaim(1) + currClaim := makeComputedClaim(3) + + prevEvent := &iconsensus.IConsensusClaimAcceptance{ + LastProcessedBlockNumber: new(big.Int).SetUint64(prevClaim.LastBlock + 1), + AppContract: prevClaim.IApplicationAddress, + Claim: *prevClaim.ClaimHash, + } + var currEvent *iconsensus.IConsensusClaimAcceptance = nil + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil) + m.On("findClaimAcceptanceEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil) + m.On("AcceptClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, ErrEventMismatch, errs[0]) +} + +// !claimAcceptanceMatch(currClaim, currEvent) +func TestAcceptClaimWithEventMismatch(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + + prevClaim := makeAcceptedClaim(1) + wrongClaim := makeComputedClaim(2) + currClaim := makeComputedClaim(3) + wrongEvent := makeAcceptanceEvent(wrongClaim) + prevEvent := makeAcceptanceEvent(prevClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil) + m.On("findClaimAcceptanceEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, wrongEvent, nil) + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, ErrEventMismatch, errs[0]) +} + +// !checkClaimsConstraint(prevClaim, currClaim) +func TestAcceptClaimWithAntecessorOutOfOrder(t *testing.T) { + m := newServiceMock() + + wrongClaim := makeComputedClaim(2) + wrongEvent := makeAcceptanceEvent(wrongClaim) + currClaim := makeComputedClaim(1) + var currEvent *iconsensus.IConsensusClaimAcceptance = nil + + prevClaims := map[common.Address]*ClaimRow{ + wrongClaim.IApplicationAddress: wrongClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil) + m.On("findClaimAcceptanceEventAndSucc", wrongClaim). + Return(&iconsensus.IConsensus{}, wrongEvent, currEvent, nil) + m.On("AcceptClaimToBlockchain", nil, currClaim). + Return(common.HexToHash("0x10"), nil) + + errs := m.acceptClaimsAndUpdateDatabase(m, big.NewInt(0)) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, ErrClaimMismatch, errs[0]) +} + +func TestErrAcceptanceMissingEvent(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + + prevClaim := makeComputedClaim(1) + var prevEvent *iconsensus.IConsensusClaimAcceptance = nil + currClaim := makeComputedClaim(2) + currEvent := makeAcceptanceEvent(currClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("findClaimAcceptanceEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, ErrMissingEvent, errs[0]) +} + +func TestUpdateEpochWithAcceptedClaimFailed(t *testing.T) { + m := newServiceMock() + endBlock := big.NewInt(0) + expectedErr := fmt.Errorf("not found") + + prevClaim := makeSubmittedClaim(1) + prevEvent := makeAcceptanceEvent(prevClaim) + currClaim := makeSubmittedClaim(2) + currEvent := makeAcceptanceEvent(currClaim) + + prevClaims := map[common.Address]*ClaimRow{ + prevClaim.IApplicationAddress: prevClaim, + } + currClaims := map[common.Address]*ClaimRow{ + currClaim.IApplicationAddress: currClaim, + } + + m.On("selectAcceptanceClaimPairsPerApp"). + Return(prevClaims, currClaims, nil).Once() + m.On("findClaimAcceptanceEventAndSucc", prevClaim, endBlock). + Return(&iconsensus.IConsensus{}, prevEvent, currEvent, nil).Once() + m.On("updateEpochWithAcceptedClaim", currClaim, currEvent.Raw.TxHash). + Return(expectedErr).Once() + + errs := m.acceptClaimsAndUpdateDatabase(m, endBlock) + assert.Equal(t, 1, len(errs)) + assert.Equal(t, expectedErr, errs[0]) +} From 613d6e21020cb09ed8c4f277baa764bd26bdc1c4 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:30:14 -0300 Subject: [PATCH 7/9] feat(evmreader): RetrieveInputs in chunks --- internal/evmreader/inputsource_adapter.go | 73 +++++++++++++++++++---- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/internal/evmreader/inputsource_adapter.go b/internal/evmreader/inputsource_adapter.go index 122161841..b26aa84bb 100644 --- a/internal/evmreader/inputsource_adapter.go +++ b/internal/evmreader/inputsource_adapter.go @@ -7,6 +7,9 @@ import ( "math/big" "github.com/cartesi/rollups-node/pkg/contracts/iinputbox" + "github.com/cartesi/rollups-node/pkg/ethutil" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -14,7 +17,9 @@ import ( // InputBox Wrapper type InputSourceAdapter struct { - inputbox *iinputbox.IInputBox + inputbox *iinputbox.IInputBox + client *ethclient.Client + inputBoxAddress common.Address } func NewInputSourceAdapter( @@ -26,29 +31,77 @@ func NewInputSourceAdapter( return nil, err } return &InputSourceAdapter{ - inputbox: inputbox, + inputbox: inputbox, + client: client, + inputBoxAddress: inputBoxAddress, }, nil } +func buildInputAddedFilterQuery( + opts *bind.FilterOpts, + inputBoxAddress common.Address, + appContract []common.Address, + index []*big.Int, +) (q ethereum.FilterQuery, err error) { + c, err := iinputbox.IInputBoxMetaData.GetAbi() + if err != nil { + return q, err + } + + var appContractRule []interface{} + for _, appContractItem := range appContract { + appContractRule = append(appContractRule, appContractItem) + } + var indexRule []interface{} + for _, indexItem := range index { + indexRule = append(indexRule, indexItem) + } + + topics, err := abi.MakeTopics( + []interface{}{c.Events["InputAdded"].ID}, + appContractRule, + indexRule, + ) + if err != nil { + return q, err + } + + q = ethereum.FilterQuery{ + Addresses: []common.Address{inputBoxAddress}, + FromBlock: new(big.Int).SetUint64(opts.Start), + Topics: topics, + } + if opts.End != nil { + q.ToBlock = new(big.Int).SetUint64(*opts.End) + } + return q, err +} + func (i *InputSourceAdapter) RetrieveInputs( opts *bind.FilterOpts, appContract []common.Address, index []*big.Int, ) ([]iinputbox.IInputBoxInputAdded, error) { + q, err := buildInputAddedFilterQuery(opts, i.inputBoxAddress, appContract, index) + if err != nil { + return nil, err + } - itr, err := i.inputbox.FilterInputAdded(opts, appContract, index) + itr, err := ethutil.ChunkedFilterLogs(opts.Context, i.client, q) if err != nil { return nil, err } - defer itr.Close() var events []iinputbox.IInputBoxInputAdded - for itr.Next() { - inputAddedEvent := itr.Event - events = append(events, *inputAddedEvent) - } - if err = itr.Error(); err != nil { - return nil, err + for log, err := range itr { + if err != nil { + return nil, err + } + ev, err := i.inputbox.ParseInputAdded(*log) + if err != nil { + return nil, err + } + events = append(events, *ev) } return events, nil } From fb1ff9c344ae6157d18b3677f678a5a89fb7d07c Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:29:31 -0300 Subject: [PATCH 8/9] feat(evmreader): RetrieveOutputExecutionEvents in chunks --- internal/evmreader/application_adapter.go | 62 +++++++++++++++++++---- internal/evmreader/output.go | 5 +- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/internal/evmreader/application_adapter.go b/internal/evmreader/application_adapter.go index 5e874dd0d..632e664ce 100644 --- a/internal/evmreader/application_adapter.go +++ b/internal/evmreader/application_adapter.go @@ -4,7 +4,12 @@ package evmreader import ( + "math/big" + appcontract "github.com/cartesi/rollups-node/pkg/contracts/iapplication" + "github.com/cartesi/rollups-node/pkg/ethutil" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" @@ -12,7 +17,9 @@ import ( // IConsensus Wrapper type ApplicationContractAdapter struct { - application *appcontract.IApplication + application *appcontract.IApplication + client *ethclient.Client + applicationAddress common.Address } func NewApplicationContractAdapter( @@ -24,7 +31,9 @@ func NewApplicationContractAdapter( return nil, err } return &ApplicationContractAdapter{ - application: applicationContract, + application: applicationContract, + applicationAddress: appAddress, + client: client, }, nil } @@ -32,23 +41,56 @@ func (a *ApplicationContractAdapter) GetConsensus(opts *bind.CallOpts) (common.A return a.application.GetConsensus(opts) } +func buildOutputExecutedFilterQuery( + opts *bind.FilterOpts, + applicationAddress common.Address, +) (q ethereum.FilterQuery, err error) { + c, err := appcontract.IApplicationMetaData.GetAbi() + if err != nil { + return q, err + } + + topics, err := abi.MakeTopics( + []interface{}{c.Events["OutputExecuted"].ID}, + ) + if err != nil { + return q, err + } + + q = ethereum.FilterQuery{ + Addresses: []common.Address{applicationAddress}, + FromBlock: new(big.Int).SetUint64(opts.Start), + Topics: topics, + } + if opts.End != nil { + q.ToBlock = new(big.Int).SetUint64(*opts.End) + } + return q, err +} + func (a *ApplicationContractAdapter) RetrieveOutputExecutionEvents( opts *bind.FilterOpts, ) ([]*appcontract.IApplicationOutputExecuted, error) { + q, err := buildOutputExecutedFilterQuery(opts, a.applicationAddress) + if err != nil { + return nil, err + } - itr, err := a.application.FilterOutputExecuted(opts) + itr, err := ethutil.ChunkedFilterLogs(opts.Context, a.client, q) if err != nil { return nil, err } - defer itr.Close() var events []*appcontract.IApplicationOutputExecuted - for itr.Next() { - outputExecutedEvent := itr.Event - events = append(events, outputExecutedEvent) - } - if err = itr.Error(); err != nil { - return nil, err + for log, err := range itr { + if err != nil { + return nil, err + } + ev, err := a.application.ParseOutputExecuted(*log) + if err != nil { + return nil, err + } + events = append(events, ev) } return events, nil } diff --git a/internal/evmreader/output.go b/internal/evmreader/output.go index a2f7cba2b..f7b9382d8 100644 --- a/internal/evmreader/output.go +++ b/internal/evmreader/output.go @@ -64,8 +64,9 @@ func (r *Service) readAndUpdateOutputs( contract := app.applicationContract opts := &bind.FilterOpts{ - Start: lastOutputCheck + 1, - End: &mostRecentBlockNumber, + Context: ctx, + Start: lastOutputCheck + 1, + End: &mostRecentBlockNumber, } outputExecutedEvents, err := contract.RetrieveOutputExecutionEvents(opts) From 4270df8e9d94bcca9f6831eeee9e6a544128ad24 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:21:03 -0300 Subject: [PATCH 9/9] chore: make fmt --- internal/merkle/proof.go | 1 + internal/merkle/proof_test.go | 6 +++--- test/validator/validator_test.go | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/merkle/proof.go b/internal/merkle/proof.go index 91005e4bc..ae79bf6a4 100644 --- a/internal/merkle/proof.go +++ b/internal/merkle/proof.go @@ -101,6 +101,7 @@ func (me *arrayChunk) get(index uint64) *common.Hash { // - `outputsBegin` is the index of the first element of `outputHashes` since genesis. // // The result array contains the siblings of all outputs. Use a a stride of TREE_DEPTH to retrieve the values of each output. +// // siblings := merkle.ComputeSiblingsMatrix(pre, outputHashes, post, index) // for i := range outputHashes { // begin := merkle.TREE_DEPTH * i diff --git a/internal/merkle/proof_test.go b/internal/merkle/proof_test.go index 4442a2b20..3d7ca8912 100644 --- a/internal/merkle/proof_test.go +++ b/internal/merkle/proof_test.go @@ -102,13 +102,13 @@ func TestComputSiblingsMatrixAssertions(t *testing.T) { index := uint64(0) _, err = ComputeSiblingsMatrix(pre, []common.Hash{}, post, index) - assert.NotNil(t, err) + assert.NotNil(t, err) _, err = ComputeSiblingsMatrix(nil, outputs, post, index) - assert.NotNil(t, err) + assert.NotNil(t, err) _, err = ComputeSiblingsMatrix(pre, outputs, nil, index) - assert.NotNil(t, err) + assert.NotNil(t, err) } // check if creating a pre context from proof and then computing the siblings diff --git a/test/validator/validator_test.go b/test/validator/validator_test.go index 9b8660e30..93180ce76 100644 --- a/test/validator/validator_test.go +++ b/test/validator/validator_test.go @@ -59,7 +59,7 @@ func (s *ValidatorRepositoryIntegrationSuite) SetupSubTest() { serviceArgs := validator.CreateInfo{ CreateInfo: service.CreateInfo{ - Name: "validator", + Name: "validator", LogLevel: slog.LevelDebug, }, Repository: s.repository, @@ -105,7 +105,6 @@ func (s *ValidatorRepositoryIntegrationSuite) TestItReturnsPristineClaim() { LastBlock: 9, } - input := model.Input{ Index: 0, BlockNumber: 9,