Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate local state index #541

Closed

Conversation

sideninja
Copy link
Member

@sideninja sideninja commented Sep 11, 2024

Description

Integrate local state index complying to the EVM client interface. A client handler was added which is a way to execute both remote and local client and compare results. We only use the local state result if the remote client fails. We also can then change this handler to be smarter in ways it chooses which client to use (for example as long as the state index is healthy use local otherwise use remote).
Please note there are a lot of changes that are results of renaming types.


For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

Summary by CodeRabbit

  • New Features

    • Introduced a new ClientHandler for enhanced EVM operations with local and remote client support.
    • Added EVMClient interface for structured EVM interactions.
    • Implemented Engine and BlockState for robust blockchain state management.
    • New integration test for state execution of transfers within the Flow EVM environment.
  • Bug Fixes

    • Improved error handling and logging for EVM operations.
  • Documentation

    • Enhanced configuration options for the emulator in testing scenarios.
  • Tests

    • Comprehensive test suite for Register functionality and new integration tests for state execution.

@sideninja sideninja changed the base branch from main to gregor/local-state/main September 11, 2024 14:57
Copy link
Contributor

coderabbitai bot commented Sep 11, 2024

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request introduces significant changes across multiple files, primarily focusing on the integration of Ethereum Virtual Machine (EVM) functionalities. Key modifications include the introduction of new structs and interfaces for handling EVM operations, the refactoring of existing structures to enhance clarity and maintainability, and the addition of comprehensive testing for state execution and transaction handling. The changes also involve renaming and restructuring fields and methods to improve accessibility and consistency throughout the codebase.

Changes

Files Change Summary
api/api.go, bootstrap/bootstrap.go Modified BlockChainAPI to use requester.EVMClient. Updated Bootstrap struct with new fields for EVM and state management, and renamed several fields to public.
models/receipt.go, models/transaction.go Added EqualReceipts function for receipt comparison. Reformatted NewReceipt function call for readability.
services/requester/client_handler.go, services/requester/evm.go, services/requester/local_client.go, services/requester/remote_client.go Introduced ClientHandler for EVM operations, defined EVMClient interface, and created LocalClient for read-only access. Refactored RemoteClient to consolidate methods.
services/state/engine.go, services/state/state.go Implemented Engine for blockchain state management and BlockState for transaction execution within the EVM context.
storage/pebble/register.go, storage/pebble/register_test.go Added Register struct for managing ledger values and introduced tests for SetValue and GetValue methods.
tests/helpers.go, tests/state_integration_test.go Modified test helpers for emulator configuration and added integration tests for state execution of transfers.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ClientHandler
    participant EVMClient
    participant LocalClient
    participant RemoteClient
    participant Engine
    participant BlockState

    User->>ClientHandler: Send transaction
    ClientHandler->>LocalClient: Execute transaction
    alt LocalClient fails
        ClientHandler->>RemoteClient: Execute transaction
    end
    ClientHandler->>Engine: Notify transaction
    Engine->>BlockState: Execute transaction
    BlockState-->>Engine: Return result
    Engine-->>ClientHandler: Return transaction status
    ClientHandler-->>User: Return transaction confirmation
Loading

🐇 In the code, a change we see,
EVM's magic, oh so free!
With structs and tests, we hop along,
Validating states, where we belong.
A blockchain dance, in code we play,
Hopping through transactions, hip-hip-hooray! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai generate interesting stats about this repository and render them as a table.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sideninja sideninja changed the base branch from gregor/local-state/main to gregor/local-state/historic September 11, 2024 14:57
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Outside diff range and nitpick comments (4)
storage/pebble/register_test.go (1)

55-120: LGTM, but consider adding more examples.

The subtest is correctly testing the SetValue and GetValue methods for multiple accounts at different heights.

However, consider adding more examples to cover additional scenarios, such as:

  • Setting and getting values for the same owner and key at non-consecutive heights.
  • Setting and getting values for the same owner and different keys at the same height.
  • Setting and getting values for different owners and the same key at the same height.
services/state/engine.go (1)

82-85: LGTM, but consider adding cleanup logic.

The Stop function is correctly implemented and marks the engine as stopped.

However, consider adding cleanup logic to gracefully shutdown the engine and release any resources.

services/state/state.go (2)

21-31: LGTM, but address the todo comment.

The struct is well-defined and contains all the necessary fields to represent the state of a block.

However, please address the todo comment to change types.StateDB to types.ReadOnlyView in the future.


138-177: LGTM, but address the todo comments.

The function is correctly implemented and initializes the block context with the necessary fields. The GetHashFunc closure allows retrieving the hash of a block by height.

However, please address the following todo comments:

  1. At line 161-162: Set the Tracer field appropriately.
  2. At line 166-167: Consider failing and executing calls using a remote client when cadence architecture is used.
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between dea4e39 and 56e4bca.

Files selected for processing (15)
  • api/api.go (2 hunks)
  • bootstrap/bootstrap.go (12 hunks)
  • models/receipt.go (2 hunks)
  • models/transaction.go (1 hunks)
  • services/requester/client_handler.go (1 hunks)
  • services/requester/evm.go (1 hunks)
  • services/requester/local_client.go (1 hunks)
  • services/requester/remote_client.go (17 hunks)
  • services/requester/remote_client_test.go (1 hunks)
  • services/state/engine.go (1 hunks)
  • services/state/state.go (1 hunks)
  • storage/pebble/register.go (1 hunks)
  • storage/pebble/register_test.go (1 hunks)
  • tests/helpers.go (2 hunks)
  • tests/state_integration_test.go (1 hunks)
Files skipped from review due to trivial changes (1)
  • models/transaction.go
Additional comments not posted (78)
services/requester/evm.go (8)

11-13: LGTM!

The code changes are approved.


15-17: LGTM!

The code changes are approved.


19-22: LGTM!

The code changes are approved.


24-27: LGTM!

The code changes are approved.


29-30: LGTM!

The code changes are approved.


32-34: LGTM!

The code changes are approved.


36-37: LGTM!

The code changes are approved.


39-40: LGTM!

The code changes are approved.

services/requester/local_client.go (7)

16-21: LGTM!

The code changes are approved.


28-33: LGTM!

The code changes are approved.


35-42: LGTM!

The code changes are approved.


60-71: LGTM!

The code changes are approved.


73-79: LGTM!

The code changes are approved.


81-87: LGTM!

The code changes are approved.


89-96: LGTM!

The code changes are approved.

storage/pebble/register_test.go (2)

11-26: LGTM!

The subtest is correctly testing the SetValue and GetValue methods at the same height.


28-53: LGTM!

The subtest is correctly testing the SetValue and GetValue methods at the latest height.

services/state/engine.go (7)

19-28: LGTM!

The Engine struct is well-defined and has all the necessary fields for the state engine.


30-51: LGTM!

The NewStateEngine function is correctly implemented and initializes all the fields of the Engine struct.


59-74: LGTM!

The Notify function is correctly implemented and handles the block execution.


76-80: LGTM!

The Run function is correctly implemented and sets up the engine to receive blocks from the publisher.


87-89: LGTM!

The Done function is correctly implemented and returns the done channel from the engine status.


91-93: LGTM!

The Ready function is correctly implemented and returns the ready channel from the engine status.


108-126: LGTM!

The executeBlock function is correctly implemented and executes the transactions in the block.

storage/pebble/register.go (8)

16-20: LGTM!

The Register struct is well-defined with appropriate fields for height, store, and synchronization.


22-28: LGTM!

The NewRegister function correctly initializes all the fields of the Register struct.


30-60: LGTM!

The GetValue method is correctly implemented with proper locking, iterator usage, and error handling. It also adheres to the interface expectation of returning nil when the value is not found.


62-77: LGTM!

The SetValue method is correctly implemented with proper locking and error handling. It delegates the storage operation to the underlying store.


79-86: LGTM!

The ValueExists method is correctly implemented by leveraging the GetValue method and checking the returned value.


88-122: LGTM!

The AllocateSlabIndex method is correctly implemented with proper locking, retrieval of the current slab index, incrementing it, and persisting the new index back to the store. It also handles the case when the slab index is not found in the store.


132-136: LGTM!

The id method correctly calculates the ledger ID by appending the owner, key, and height bytes, following the schema mentioned in the comments.


138-145: LGTM!

The idUpper and idLower methods correctly calculate the upper and lower bounds for the ledger ID range. The upper bound is exclusive and increments the height by 1, while the lower bound always uses a height of 0.

Also applies to: 147-153

tests/state_integration_test.go (1)

22-163: Comprehensive state execution test.

The test function Test_StateExecution_Transfers is well-structured and covers important aspects of state execution and balance updates. It follows a logical flow and tests the following scenarios:

  1. Setting up the emulator and required services.
  2. Configuring and starting the state index, API server, and event ingestion.
  3. Verifying the initial balance of a test address.
  4. Sending EVM transactions and waiting for new block events.
  5. Verifying the updated balances after each transaction.
  6. Reading balances at historic heights and verifying the expected values.

The test function is comprehensive and ensures the correctness of state execution and balance updates across different block heights.

services/state/state.go (3)

33-65: LGTM!

The function is correctly implemented and initializes all the necessary fields of the BlockState struct.


67-114: LGTM, but verify the error handling behavior.

The function is correctly implemented and supports executing both direct calls and transaction calls. It also verifies the result receipt against the retrieved receipt to ensure state consistency.

However, please verify the error handling behavior mentioned in the todo comment at line 94-95. Ensure that restarting and retrying the service on error is the desired behavior.


116-133: LGTM!

The function is correctly implemented and allows performing a call without modifying the state.

models/receipt.go (1)

74-141: LGTM!

The EqualReceipts function is well-structured and correctly compares the geth Receipt type with the custom Receipt type. It handles nil receipts, compares logs, and compares receipt data fields. The function returns a boolean indicating whether the receipts are equal and a slice of errors containing any mismatches found during the comparison.

The code is readable, maintainable, and follows best practices.

services/requester/remote_client_test.go (1)

Line range hint 213-227: LGTM!

The changes to the createEVM test helper function are consistent with the updated return type of *RemoteClient. The instantiation of the RemoteClient struct uses the same fields as the previous EVM struct, indicating that the RemoteClient struct likely has a similar structure and functionality.

The changes are localized to the test helper function and do not affect the behavior of the tests.

services/requester/client_handler.go (12)

29-37: LGTM!

The ClientHandler struct is well-defined with appropriate fields to handle EVM operations using remote and local clients.


39-64: LGTM!

The NewClientHandler function is correctly implemented and handles the creation of a new ClientHandler instance with the provided dependencies.


66-69: LGTM!

The SendRawTransaction method is correctly implemented and always uses the remote client to send the raw transaction.


71-86: LGTM!

The GetBalance method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


88-104: LGTM!

The Call method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


106-122: LGTM!

The EstimateGas method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


124-139: LGTM!

The GetNonce method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


141-156: LGTM!

The GetCode method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


158-169: LGTM!

The GetLatestEVMHeight method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


171-187: LGTM!

The GetStorageAt method is correctly implemented and uses the handleCall function to execute the call using both local and remote clients and handle errors.


189-213: LGTM!

The localClient method is correctly implemented and handles the creation of a new LocalClient instance for a given block height using the retrieved block and other dependencies.


215-290: LGTM!

The handleCall function is well-implemented and handles the execution and error handling of local and remote calls effectively. It covers various error scenarios and returns the appropriate result based on the conditions. The use of goroutines for concurrent execution and the comparison of results in case of no errors adds to the robustness of the function.

tests/helpers.go (2)

85-93: Approve addition of SkipTransactionValidation field with a note of caution.

The addition of the SkipTransactionValidation field to the emulator server configuration is approved.

This field could be useful in certain test scenarios to improve performance or test specific behaviors by skipping transaction validation.

However, please ensure that skipping validation in tests does not inadvertently hide any potential issues that could occur in production where validation is enabled. Consider adding some tests with validation enabled to catch such issues.


330-331: Approve line break change.

The line break added before the closing parenthesis of the evmSign function parameter list is approved.

This change enhances code readability without altering the function's behavior.

bootstrap/bootstrap.go (10)

36-36: LGTM!

The addition of the Registers field to the Storages struct is approved.


48-57: LGTM!

The changes to the Bootstrap struct, including the addition of new fields and renaming of existing fields to uppercase, are approved. These changes improve the organization and consistency of the struct.


75-83: LGTM!

The changes to the New function, which initialize the new fields added to the Bootstrap struct, are approved. The initialization logic is correct and consistent with the struct changes.


Line range hint 93-141: LGTM!

The changes to the StartEventIngestion method, which update the usage of the Bootstrap struct fields to match the new public field names, are approved. The field names are used correctly and consistently throughout the method.


156-165: LGTM!

The changes to the StartTraceDownloader method, which update the usage of the Bootstrap struct fields to match the new public field names, are approved. The field names are used correctly and consistently throughout the method.


170-174: LGTM!

The change to the StopTraceDownloader method, which updates the usage of the Bootstrap struct field to match the new public field name Traces, is approved. The field name is used correctly.


178-183: LGTM!

The change to the StopEventIngestion method, which updates the usage of the Bootstrap struct field to match the new public field name Events, is approved. The field name is used correctly.


185-201: LGTM!

The addition of the StartStateIndex method to the Bootstrap struct is approved. The method correctly initializes the state indexing engine using the new State field and handles the startup process appropriately.


203-208: LGTM!

The addition of the StopStateIndex method to the Bootstrap struct is approved. The method correctly handles the stopping of the state indexing engine by checking if the State field is nil before calling the Stop method.


Line range hint 214-341: LGTM!

The changes to the StartAPIServer method, which update the usage of the Bootstrap struct fields to match the new public field names and initialize the requester.NewClientHandler with the new EVMClient field, are approved. The field names are used correctly and consistently throughout the method, and the initialization of the client handler is valid.

services/requester/remote_client.go (14)

Line range hint 84-101: LGTM!

The RemoteClient struct looks good. It provides a clear structure for the remote client implementation and contains all the necessary dependencies and configurations.


Line range hint 103-193: LGTM!

The NewRemote constructor function looks good. It properly initializes the RemoteClient instance with the provided dependencies and configurations. It also performs necessary checks on the COA account and creates the COA resource if required.


Line range hint 195-252: LGTM!

The SendRawTransaction method looks good. It properly validates the transaction, checks the gas price, builds a Flow transaction with the EVM transaction data, and sends it to the transaction pool.


Line range hint 254-302: LGTM!

The buildTransaction method looks good. It properly builds a Flow transaction from the provided script and arguments, concurrently retrieves the necessary information, sets the transaction fields, and signs the transaction envelope.


Line range hint 304-348: LGTM!

The GetBalance method looks good. It properly retrieves the balance of an address at a specific EVM height by converting the height, executing a script, and returning the balance as a big integer.


Line range hint 350-401: LGTM!

The GetNonce method looks good. It properly retrieves the nonce of an address at a specific EVM height by converting the height, executing a script, and returning the nonce as a uint64.


Line range hint 403-424: LGTM!

The stateAt method looks good. It properly retrieves the state database at a specific EVM height by converting the height, creating a remote ledger, and returning a new state database instance.


Line range hint 426-439: LGTM!

The GetStorageAt method looks good. It properly retrieves the storage value at a specific address and hash at a specific EVM height by retrieving the state database and getting the storage value.


Line range hint 441-495: LGTM!

The Call method looks good. It properly executes a read-only EVM call at a specific height by converting the height, executing a script, parsing the result, and returning the returned data.


Line range hint 497-551: LGTM!

The EstimateGas method looks good. It properly estimates the gas consumption of an EVM transaction at a specific height by converting the height, executing a script, parsing the result, and returning the gas consumed.


Line range hint 553-605: LGTM!

The GetCode method looks good. It properly retrieves the code of an address at a specific EVM height by converting the height, executing a script, converting the result to bytes, and returning the code.


Line range hint 607-631: LGTM!

The GetLatestEVMHeight method looks good. It properly retrieves the latest EVM height by executing a script at the latest block height, converting the result to uint64, and returning the height.


Line range hint 633-658: LGTM!

The getSignerNetworkInfo method looks good. It properly retrieves the signer account information from the network and returns the key index and sequence number of the signer account.


Line range hint 660-678: LGTM!

The replaceAddresses method looks good. It properly replaces the addresses in the script based on the network by iterating over the system contracts, replacing the import statements with the actual contract addresses, and replacing the COA address if used.

api/api.go (2)

86-86: LGTM!

The change to the evm field type from requester.Requester to requester.EVMClient is approved.

Using a more specialized EVMClient interface improves type safety and makes the EVM interactions clearer.


99-99: LGTM!

The change to the evm parameter type from requester.Requester to requester.EVMClient in the constructor is approved.

This change is consistent with the BlockChainAPI struct field type change and ensures proper instantiation.

Comment on lines 44 to 58
func (l *LocalClient) Call(
ctx context.Context,
data []byte,
from common.Address,
evmHeight int64,
) ([]byte, error) {
res, err := l.state.Call(from, data)
if err != nil {
return nil, err
}

// todo what if it failed?

return res.ReturnedData, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle the case of a failed call.

The method is mostly implemented correctly, but the case of a failed call is not handled.

Consider adding an error check and return the error if the call fails:

res, err := l.state.Call(from, data)
if err != nil {
    return nil, err
}
if res.Failed() {
    return nil, fmt.Errorf("call failed: %s", res.Err)
}
return res.ReturnedData, nil

}

func (l *LocalClient) GetLatestEVMHeight(ctx context.Context) (uint64, error) {
return l.blocks.LatestEVMHeight()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that this method makes sense for the local client. The requester would use a Cadence script to fetch the latest EVM block on the Flow network, which doesn't have to be the same as what any EVM Gateway operator has ingested.

address common.Address,
evmHeight int64,
) (*big.Int, error) {
bal := l.state.GetBalance(address)
Copy link
Collaborator

@m-Peter m-Peter Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we know that the *state.BlockState matches the given evmHeight 🤔 ? The same goes for all methods that accept historical blocks. I can see that the ClientHandler takes care of that, it's just seems not very intuitive to have a function argument which is unused/ignored.

return err
}

ctx, err := s.blockContext(receipt)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ctx, err := s.blockContext(receipt)
ctx, err := s.blockContext(receipt)
if err != nil {
return err
}

return nil, err
}

return bv.DryRunTransaction(tx, from)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 👏

}

if ok, errs := models.EqualReceipts(res.Receipt(), receipt); !ok {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that we may be able to get away without this models.EqualReceipts method, if we used the blocks'

  • TransactionsHashRoot
  • ReceiptsRoot
    fields.
    We do something similar:
txHashes := evmTypes.TransactionHashes{}
for _, tx := range e.transactions {
	txHashes = append(txHashes, tx.Hash())
}
if e.block.TransactionHashRoot != txHashes.RootHash() {
	return nil, fmt.Errorf(
		"block %d references missing transaction/s",
		e.block.Height,
	)
}

And

b.ReceiptRoot = gethTypes.DeriveSha(receipts, gethTrie.NewStackTrie(nil))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that it is more bullet-proof, to compare them this way. But can be done in a follow-up PR.

# Conflicts:
#	services/requester/client_handler.go
#	services/requester/local_client.go
@sideninja
Copy link
Member Author

mreged into feature/state

@sideninja sideninja closed this Sep 16, 2024
@sideninja sideninja mentioned this pull request Sep 16, 2024
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

2 participants