From b6d2f44e1c9dfa79d8fe6793171bd2413652b283 Mon Sep 17 00:00:00 2001 From: Francisco Moura Date: Tue, 3 Sep 2024 11:45:19 -0300 Subject: [PATCH] feat(evm-reader): Read output execution statuses --- internal/evmreader/application_adapter.go | 55 ++++++++++++++ internal/evmreader/evmreader.go | 13 ++-- internal/evmreader/output.go | 88 +++++++++++++++++++++++ internal/node/model/models.go | 15 ++-- internal/repository/evmreader.go | 9 +++ 5 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 internal/evmreader/application_adapter.go create mode 100644 internal/evmreader/output.go diff --git a/internal/evmreader/application_adapter.go b/internal/evmreader/application_adapter.go new file mode 100644 index 000000000..4b8becf4a --- /dev/null +++ b/internal/evmreader/application_adapter.go @@ -0,0 +1,55 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package evmreader + +import ( + appcontract "github.com/cartesi/rollups-node/pkg/contracts/application" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" +) + +// IConsensus Wrapper +type ApplicationContractAdapter struct { + application *appcontract.Application +} + +func NewApplicationContractAdapter( + appAddress common.Address, + client *ethclient.Client, +) (*ApplicationContractAdapter, error) { + applicationContract, err := appcontract.NewApplication(appAddress, client) + if err != nil { + return nil, err + } + return &ApplicationContractAdapter{ + application: applicationContract, + }, nil +} + +func (a *ApplicationContractAdapter) GetConsensus(opts *bind.CallOpts) (common.Address, error) { + return a.application.GetConsensus(opts) +} + +func (a *ApplicationContractAdapter) RetrieveOutputExecutionEvents( + opts *bind.FilterOpts, +) ([]*appcontract.ApplicationOutputExecuted, error) { + + itr, err := a.application.FilterOutputExecuted(opts) + if err != nil { + return nil, err + } + defer itr.Close() + + var events []*appcontract.ApplicationOutputExecuted + for itr.Next() { + outputExecutedEvent := itr.Event + events = append(events, outputExecutedEvent) + } + err = itr.Error() + if err != nil { + return nil, err + } + return events, nil +} diff --git a/internal/evmreader/evmreader.go b/internal/evmreader/evmreader.go index 6f4b7ad2f..0f7f0b91c 100644 --- a/internal/evmreader/evmreader.go +++ b/internal/evmreader/evmreader.go @@ -13,6 +13,7 @@ import ( "slices" . "github.com/cartesi/rollups-node/internal/node/model" + appcontract "github.com/cartesi/rollups-node/pkg/contracts/application" "github.com/cartesi/rollups-node/pkg/contracts/iconsensus" "github.com/cartesi/rollups-node/pkg/contracts/inputbox" "github.com/ethereum/go-ethereum" @@ -41,10 +42,11 @@ type EvmReaderRepository interface { GetNodeConfig(ctx context.Context) (*NodePersistentConfig, error) GetEpoch(ctx context.Context, indexKey uint64, appAddressKey Address) (*Epoch, error) GetPreviousSubmittedClaims(ctx context.Context, app Address, lastBlock uint64) ([]Epoch, error) - StoreClaimsTransaction(ctx context.Context, - app Address, - claims []*Epoch, - mostRecentBlockNumber uint64, + StoreClaimsTransaction( + ctx context.Context, app Address, claims []*Epoch, mostRecentBlockNumber uint64, + ) error + UpdateOutputExecutedTransaction( + ctx context.Context, app Address, executedOutputs []uint64, blockNumber uint64, ) error } @@ -68,6 +70,9 @@ type ConsensusContract interface { type ApplicationContract interface { GetConsensus(opts *bind.CallOpts) (Address, error) + RetrieveOutputExecutionEvents( + opts *bind.FilterOpts, + ) ([]*appcontract.ApplicationOutputExecuted, error) } type ContractFactory interface { diff --git a/internal/evmreader/output.go b/internal/evmreader/output.go new file mode 100644 index 000000000..f8b8e5988 --- /dev/null +++ b/internal/evmreader/output.go @@ -0,0 +1,88 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package evmreader + +import ( + "context" + "log/slog" + + . "github.com/cartesi/rollups-node/internal/node/model" + "github.com/ethereum/go-ethereum/accounts/abi/bind" +) + +func (r *EvmReader) checkForOutputExecution( + ctx context.Context, + apps []application, + mostRecentBlockNumber uint64, +) { + + appAddresses := appToAddresses(apps) + + slog.Debug("Checking for new Output Executed Events", "apps", appAddresses) + + for _, app := range apps { + + LastOutputCheck := app.LastOutputCheckBlock + + // Safeguard: Only check blocks starting from the block where the InputBox + // contract was deployed as Inputs can be added to that same block + if LastOutputCheck < r.inputBoxDeploymentBlock { + LastOutputCheck = r.inputBoxDeploymentBlock + } + + if mostRecentBlockNumber > LastOutputCheck { + + slog.Info("Checking output execution for applications", + "apps", appAddresses, + "last output check block", LastOutputCheck, + "most recent block", mostRecentBlockNumber) + + r.readAndUpdateOutputs(ctx, app, LastOutputCheck, mostRecentBlockNumber) + + } else if mostRecentBlockNumber < LastOutputCheck { + slog.Warn( + "Not reading output execution: most recent block is lower than the last processed one", //nolint:lll + "apps", appAddresses, + "last output check block", LastOutputCheck, + "most recent block", mostRecentBlockNumber, + ) + } else { + slog.Info("Not reading output execution: already checked the most recent blocks", + "apps", appAddresses, + "last output check block", LastOutputCheck, + "most recent block", mostRecentBlockNumber, + ) + } + } + +} + +func (r *EvmReader) readAndUpdateOutputs(ctx context.Context, app application, lastOutputCheck, mostRecentBlockNumber uint64) { + + contract := app.applicationContract + + opts := &bind.FilterOpts{ + Start: lastOutputCheck + 1, + End: &mostRecentBlockNumber, + } + + outputExecutedEvents, err := contract.RetrieveOutputExecutionEvents(opts) + if err != nil { + slog.Error("Error reading output events", "app", app.ContractAddress, "error", err) + return + } + + // Should we check the output hash?? + var executedOutputs []uint64 + for _, event := range outputExecutedEvents { + slog.Info("Output executed", "app", app, "index", event.OutputIndex) + executedOutputs = append(executedOutputs, event.OutputIndex) + } + + err = r.repository.UpdateOutputExecutedTransaction(ctx, app.ContractAddress, executedOutputs, mostRecentBlockNumber) + if err != nil { + slog.Error("Error storing output execution statuses", "app", app, "error", err) + } + +} diff --git a/internal/node/model/models.go b/internal/node/model/models.go index f711ddf03..7aaf4045c 100644 --- a/internal/node/model/models.go +++ b/internal/node/model/models.go @@ -62,13 +62,14 @@ type NodePersistentConfig struct { } type Application struct { - Id uint64 - ContractAddress Address - TemplateHash Hash - LastProcessedBlock uint64 - Status ApplicationStatus - IConsensusAddress Address - LastClaimCheckBlock uint64 + Id uint64 + ContractAddress Address + TemplateHash Hash + LastProcessedBlock uint64 + Status ApplicationStatus + IConsensusAddress Address + LastClaimCheckBlock uint64 + LastOutputCheckBlock uint64 } type Epoch struct { diff --git a/internal/repository/evmreader.go b/internal/repository/evmreader.go index d58914de5..06ddd3766 100644 --- a/internal/repository/evmreader.go +++ b/internal/repository/evmreader.go @@ -371,3 +371,12 @@ func (pg *Database) StoreClaimsTransaction( return nil } + +func (pg *Database) UpdateOutputExecutedTransaction( + ctx context.Context, + app Address, + executedOutputs []uint64, + blockNumber uint64, +) error { + +}