From 083f211fcf5348cb5dd8d9f94724336a30b667a3 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:24:56 -0300 Subject: [PATCH 1/2] chore: generate rollups-contracts bindings with getNumberOfSubmittedClaims --- pkg/contracts/iconsensus/iconsensus.go | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/pkg/contracts/iconsensus/iconsensus.go b/pkg/contracts/iconsensus/iconsensus.go index 3b92ebe5b..d0071cdd2 100644 --- a/pkg/contracts/iconsensus/iconsensus.go +++ b/pkg/contracts/iconsensus/iconsensus.go @@ -31,7 +31,7 @@ var ( // IConsensusMetaData contains all meta data concerning the IConsensus contract. var IConsensusMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"getEpochLength\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNumberOfAcceptedClaims\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOutputsMerkleRootValid\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"submitClaim\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"ClaimAccepted\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ClaimSubmitted\",\"inputs\":[{\"name\":\"submitter\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"appContract\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"NotEpochFinalBlock\",\"inputs\":[{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"epochLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NotFirstClaim\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NotPastBlock\",\"inputs\":[{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"currentBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", + ABI: "[{\"type\":\"function\",\"name\":\"getEpochLength\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNumberOfAcceptedClaims\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNumberOfSubmittedClaims\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOutputsMerkleRootValid\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"submitClaim\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"ClaimAccepted\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ClaimSubmitted\",\"inputs\":[{\"name\":\"submitter\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"appContract\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"outputsMerkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"NotEpochFinalBlock\",\"inputs\":[{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"epochLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NotFirstClaim\",\"inputs\":[{\"name\":\"appContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"NotPastBlock\",\"inputs\":[{\"name\":\"lastProcessedBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"currentBlockNumber\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", } // IConsensusABI is the input ABI used to generate the binding from. @@ -242,6 +242,37 @@ func (_IConsensus *IConsensusCallerSession) GetNumberOfAcceptedClaims() (*big.In return _IConsensus.Contract.GetNumberOfAcceptedClaims(&_IConsensus.CallOpts) } +// GetNumberOfSubmittedClaims is a free data retrieval call binding the contract method 0xee5e0faa. +// +// Solidity: function getNumberOfSubmittedClaims() view returns(uint256) +func (_IConsensus *IConsensusCaller) GetNumberOfSubmittedClaims(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _IConsensus.contract.Call(opts, &out, "getNumberOfSubmittedClaims") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetNumberOfSubmittedClaims is a free data retrieval call binding the contract method 0xee5e0faa. +// +// Solidity: function getNumberOfSubmittedClaims() view returns(uint256) +func (_IConsensus *IConsensusSession) GetNumberOfSubmittedClaims() (*big.Int, error) { + return _IConsensus.Contract.GetNumberOfSubmittedClaims(&_IConsensus.CallOpts) +} + +// GetNumberOfSubmittedClaims is a free data retrieval call binding the contract method 0xee5e0faa. +// +// Solidity: function getNumberOfSubmittedClaims() view returns(uint256) +func (_IConsensus *IConsensusCallerSession) GetNumberOfSubmittedClaims() (*big.Int, error) { + return _IConsensus.Contract.GetNumberOfSubmittedClaims(&_IConsensus.CallOpts) +} + // IsOutputsMerkleRootValid is a free data retrieval call binding the contract method 0xe5cc8664. // // Solidity: function isOutputsMerkleRootValid(address appContract, bytes32 outputsMerkleRoot) view returns(bool) From f5fb2deaf8edd970feaa2a79c71ade1112ac38f3 Mon Sep 17 00:00:00 2001 From: Marcelo Politzer <251334+mpolitzer@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:10:56 -0300 Subject: [PATCH 2/2] feat: migrate claimer from ChunkedFilterLogs to ethutil.FindTransitions --- Makefile | 8 +- internal/claimer/blockchain.go | 185 ++++++++++++++------------------- 2 files changed, 80 insertions(+), 113 deletions(-) diff --git a/Makefile b/Makefile index 18309719c..c46b02ad3 100644 --- a/Makefile +++ b/Makefile @@ -18,10 +18,10 @@ TARGET_OS?=$(shell uname) export TARGET_OS ROLLUPS_NODE_VERSION := 2.0.0-alpha.9 -ROLLUPS_CONTRACTS_VERSION := 2.1.1 +ROLLUPS_CONTRACTS_VERSION := 2.2.0 ROLLUPS_CONTRACTS_URL:=https://github.com/cartesi/rollups-contracts/releases/download/ ROLLUPS_CONTRACTS_ARTIFACT:=rollups-contracts-$(ROLLUPS_CONTRACTS_VERSION)-artifacts.tar.gz -ROLLUPS_CONTRACTS_SHA256:=2e7a105d656de2adafad6439a5ff00f35b997aaf27972bd1becc33dea8817861 +ROLLUPS_CONTRACTS_SHA256:=31c20a8c50f794185957ebd6e554fc99c8e01f0fdf9a80628d031fb0edc7091d ROLLUPS_PRT_CONTRACTS_VERSION := 2.0.1 ROLLUPS_PRT_CONTRACTS_URL:=https://github.com/cartesi/dave/releases/download/ ROLLUPS_PRT_CONTRACTS_ARTIFACT:=cartesi-rollups-prt-contract-artifacts.tar.gz @@ -116,9 +116,9 @@ env: @echo export CARTESI_BLOCKCHAIN_WS_ENDPOINT="ws://localhost:8545" @echo export CARTESI_BLOCKCHAIN_ID="31337" @echo export CARTESI_CONTRACTS_INPUT_BOX_ADDRESS="0x1b51e2992A2755Ba4D6F7094032DF91991a0Cfac" - @echo export CARTESI_CONTRACTS_AUTHORITY_FACTORY_ADDRESS="0x5a3368b30174d389aFd205a46bAd35BBE6709b8a" + @echo export CARTESI_CONTRACTS_AUTHORITY_FACTORY_ADDRESS="0x5E96408CFE423b01dADeD3bc867E6013135990cc" @echo export CARTESI_CONTRACTS_APPLICATION_FACTORY_ADDRESS="0x26E758238CB6eC5aB70ce0dd52aF2d7b82e1972E" - @echo export CARTESI_CONTRACTS_SELF_HOSTED_APPLICATION_FACTORY_ADDRESS="0x870240e83b1181b419f18303D4ccC56574De2931" + @echo export CARTESI_CONTRACTS_SELF_HOSTED_APPLICATION_FACTORY_ADDRESS="0x010D3CbB4223F5bCc7b7B03cEE59f3aAea8eDb8A" @echo export CARTESI_CONTRACTS_DAVE_APP_FACTORY_ADDRESS="0x96cD319eBD67DF6b753766ec000fe639dFba9F6b" @echo export CARTESI_AUTH_MNEMONIC=\"test test test test test test test test test test test junk\" @echo export CARTESI_DATABASE_CONNECTION="postgres://postgres:password@localhost:5432/rollupsdb?sslmode=disable" diff --git a/internal/claimer/blockchain.go b/internal/claimer/blockchain.go index e148460dd..841be9df2 100644 --- a/internal/claimer/blockchain.go +++ b/internal/claimer/blockchain.go @@ -6,7 +6,6 @@ package claimer import ( "context" "fmt" - "iter" "log/slog" "math/big" @@ -15,8 +14,6 @@ import ( "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" "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/core/types" @@ -103,22 +100,6 @@ func (self *claimerBlockchain) submitClaimToBlockchain( return txHash, err } -func unwrapClaimSubmitted( - ic *iconsensus.IConsensus, - pull func() (log *types.Log, err error, ok bool), -) ( - *iconsensus.IConsensusClaimSubmitted, - bool, - error, -) { - log, err, ok := pull() - if !ok || err != nil { - return nil, false, err - } - ev, err := ic.ParseClaimSubmitted(*log) - return ev, true, err -} - // scan the event stream for a claimSubmitted event that matches claim. // return this event and its successor func (self *claimerBlockchain) findClaimSubmittedEventAndSucc( @@ -137,68 +118,52 @@ func (self *claimerBlockchain) findClaimSubmittedEventAndSucc( return nil, nil, nil, err } - // filter must match: - // - `ClaimSubmitted` events - // - submitter == nil (any) - // - appContract == claim.IApplicationAddress - c, err := iconsensus.IConsensusMetaData.GetAbi() - topics, err := abi.MakeTopics( - []any{c.Events[model.MonitoredEvent_ClaimSubmitted.String()].ID}, - nil, - []any{application.IApplicationAddress}, - ) - if err != nil { - return nil, nil, nil, err - } + oracle := func(ctx context.Context, block uint64) (*big.Int, error) { + callOpts := &bind.CallOpts{ + Context: ctx, + BlockNumber: new(big.Int).SetUint64(block), + } + numSubmittedClaims, err := ic.GetNumberOfSubmittedClaims(callOpts) - it, err := self.filter.ChunkedFilterLogs(ctx, self.client, ethereum.FilterQuery{ - FromBlock: new(big.Int).SetUint64(epoch.LastBlock), - ToBlock: endBlock, - Addresses: []common.Address{application.IConsensusAddress}, - Topics: topics, - }) - if err != nil { - return nil, nil, nil, err + if err != nil { + return nil, fmt.Errorf("failed to get number of submitted claims at block %d: %w", block, err) + } + return numSubmittedClaims, nil } - // pull events instead of iterating - next, stop := iter.Pull2(it) - defer stop() - for { - event, ok, err := unwrapClaimSubmitted(ic, next) - if !ok || err != nil { - return ic, event, nil, err + events := []*iconsensus.IConsensusClaimSubmitted{} + onHit := func(block uint64) error { + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: block, + End: &block, } - lastBlock := event.LastProcessedBlockNumber.Uint64() - - if claimSubmittedEventMatches(application, epoch, event) { - // found the event, does it has a successor? try to fetch it - succ, ok, err := unwrapClaimSubmitted(ic, next) - if !ok || err != nil { - return ic, event, nil, err + claimSubmittedEvents, err := ic.FilterClaimSubmitted(filterOpts, nil, []common.Address{application.IApplicationAddress}) + if err != nil { + return fmt.Errorf("failed to retrieve inputs at block %d: %w", block, err) + } + for claimSubmittedEvents.Next() { + err := claimSubmittedEvents.Error() + if err != nil { + return fmt.Errorf("failed to iterate submitted claim events: %w", err) } - return ic, event, succ, err - } else if lastBlock > epoch.LastBlock { - err = fmt.Errorf("No matching claim, searched up to %v", event) - return nil, nil, nil, err + events = append(events, claimSubmittedEvents.Event) } + return nil } -} -func unwrapClaimAccepted( - ic *iconsensus.IConsensus, - pull func() (log *types.Log, err error, ok bool), -) ( - *iconsensus.IConsensusClaimAccepted, - bool, - error, -) { - log, err, ok := pull() - if !ok || err != nil { - return nil, false, err + _, err = ethutil.FindTransitions(ctx, epoch.LastBlock, endBlock.Uint64(), nil, oracle, onHit) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to walk input transitions: %w", err) + } + + if len(events) == 0 { + return ic, nil, nil, nil + } else if len(events) == 1 { + return ic, events[0], nil, nil + } else { + return ic, events[0], events[1], nil } - ev, err := ic.ParseClaimAccepted(*log) - return ev, true, err } // scan the event stream for a claimAccepted event that matches claim. @@ -219,49 +184,51 @@ func (self *claimerBlockchain) findClaimAcceptedEventAndSucc( return nil, nil, nil, err } - // filter must match: - // - `ClaimAccepted` events - // - appContract == claim.IApplicationAddress - c, err := iconsensus.IConsensusMetaData.GetAbi() - topics, err := abi.MakeTopics( - []any{c.Events[model.MonitoredEvent_ClaimAccepted.String()].ID}, - []any{application.IApplicationAddress}, - ) - if err != nil { - return nil, nil, nil, err - } + oracle := func(ctx context.Context, block uint64) (*big.Int, error) { + callOpts := &bind.CallOpts{ + Context: ctx, + BlockNumber: new(big.Int).SetUint64(block), + } + numAcceptedClaims, err := ic.GetNumberOfAcceptedClaims(callOpts) - it, err := self.filter.ChunkedFilterLogs(ctx, self.client, ethereum.FilterQuery{ - FromBlock: new(big.Int).SetUint64(epoch.LastBlock), - ToBlock: endBlock, - Addresses: []common.Address{application.IConsensusAddress}, - Topics: topics, - }) - if err != nil { - return nil, nil, nil, err + if err != nil { + return nil, fmt.Errorf("failed to get number of Accepted claims at block %d: %w", block, err) + } + return numAcceptedClaims, nil } - // pull events instead of iterating - next, stop := iter.Pull2(it) - defer stop() - for { - event, ok, err := unwrapClaimAccepted(ic, next) - if !ok || err != nil { - return ic, event, nil, err + events := []*iconsensus.IConsensusClaimAccepted{} + onHit := func(block uint64) error { + filterOpts := &bind.FilterOpts{ + Context: ctx, + Start: block, + End: &block, } - lastBlock := event.LastProcessedBlockNumber.Uint64() - - if claimAcceptedEventMatches(application, epoch, event) { - // found the event, does it has a successor? try to fetch it - succ, ok, err := unwrapClaimAccepted(ic, next) - if !ok || err != nil { - return ic, event, nil, err + claimAcceptedEvents, err := ic.FilterClaimAccepted(filterOpts, []common.Address{application.IApplicationAddress}) + if err != nil { + return fmt.Errorf("failed to retrieve inputs at block %d: %w", block, err) + } + for claimAcceptedEvents.Next() { + err := claimAcceptedEvents.Error() + if err != nil { + return fmt.Errorf("failed to iterate submitted claim events: %w", err) } - return ic, event, succ, err - } else if lastBlock > epoch.LastBlock { - err = fmt.Errorf("No matching claim, searched up to %v", event) - return nil, nil, nil, err + events = append(events, claimAcceptedEvents.Event) } + return nil + } + + _, err = ethutil.FindTransitions(ctx, epoch.LastBlock, endBlock.Uint64(), nil, oracle, onHit) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to walk input transitions: %w", err) + } + + if len(events) == 0 { + return ic, nil, nil, nil + } else if len(events) == 1 { + return ic, events[0], nil, nil + } else { + return ic, events[0], events[1], nil } }