Skip to content

Commit

Permalink
Ethereum sync (#2052)
Browse files Browse the repository at this point in the history
* Add the to_block to the read_events.
* Remove the dependency on linera-ethereum and move the relevant types
to linera-sdk.
* Change the Ethereum smart contract so that it now takes a target block
number for the operations.
* Move the documentation of the types from linera-sdk to linera-ethereum.
  • Loading branch information
MathieuDutSik authored May 28, 2024
1 parent 1faf723 commit de04d31
Show file tree
Hide file tree
Showing 13 changed files with 88 additions and 43 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ getrandom = { version = "0.2.12", default-features = false, features = ["custom"
hex = "0.4.3"
linera-sdk = { path = "../linera-sdk" }
linera-views = { path = "../linera-views", default-features = false }
linera-ethereum = { path = "../linera-ethereum" }
log = "0.4.20"
num-bigint = "0.4.3"
num-traits = "0.2.16"
Expand Down
1 change: 0 additions & 1 deletion examples/ethereum-tracker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ edition = "2021"
[dependencies]
alloy = { workspace = true, default-features = false, features = ["rpc-types-eth"] }
async-graphql.workspace = true
linera-ethereum.workspace = true
linera-sdk = { workspace = true, features = [ "ethereum" ] }
serde.workspace = true

Expand Down
43 changes: 29 additions & 14 deletions examples/ethereum-tracker/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
mod state;

use ethereum_tracker::{EthereumTrackerAbi, InstantiationArgument, U256Cont};
use linera_ethereum::client::EthereumQueries as _;
use linera_sdk::{
base::WithContractAbi,
ethereum::{EthereumClient, EthereumDataType},
ethereum::{EthereumClient, EthereumDataType, EthereumQueries as _},
views::{RootView, View, ViewStorageContext},
Contract, ContractRuntime,
};
Expand Down Expand Up @@ -42,15 +41,21 @@ impl Contract for EthereumTrackerContract {
async fn instantiate(&mut self, argument: InstantiationArgument) {
// Validate that the application parameters were configured correctly.
self.runtime.application_parameters();
self.state.argument.set(argument);
self.state.last_block.set(0);
self.read_initial().await;
let InstantiationArgument {
ethereum_endpoint,
contract_address,
start_block,
} = argument;
self.state.ethereum_endpoint.set(ethereum_endpoint);
self.state.contract_address.set(contract_address);
self.state.start_block.set(start_block);
self.read_initial(start_block).await;
}

async fn execute_operation(&mut self, operation: Self::Operation) -> Self::Response {
// The only input is updating the database
match operation {
Self::Operation::Update => self.update().await,
Self::Operation::Update { to_block } => self.update(to_block).await,
}
}

Expand All @@ -65,18 +70,22 @@ impl Contract for EthereumTrackerContract {

impl EthereumTrackerContract {
fn get_endpoints(&self) -> (EthereumClient, String) {
let argument = self.state.argument.get();
let url = argument.ethereum_endpoint.clone();
let contract_address = argument.contract_address.clone();
let url = self.state.ethereum_endpoint.get().clone();
let contract_address = self.state.contract_address.get().clone();
let ethereum_client = EthereumClient { url };
(ethereum_client, contract_address)
}

async fn read_initial(&mut self) {
async fn read_initial(&mut self, start_block: u64) {
let event_name_expanded = "Initial(address,uint256)";
let (ethereum_client, contract_address) = self.get_endpoints();
let events = ethereum_client
.read_events(&contract_address, event_name_expanded, 0)
.read_events(
&contract_address,
event_name_expanded,
start_block,
start_block + 1,
)
.await
.expect("Read the Initial event");
assert_eq!(events.len(), 1);
Expand All @@ -91,14 +100,20 @@ impl EthereumTrackerContract {
self.state.accounts.insert(&address, value).unwrap();
}

async fn update(&mut self) {
async fn update(&mut self, to_block: u64) {
let event_name_expanded = "Transfer(address indexed,address indexed,uint256)";
let (ethereum_client, contract_address) = self.get_endpoints();
let start_block = self.state.last_block.get_mut();
let start_block = self.state.start_block.get_mut();
let events = ethereum_client
.read_events(&contract_address, event_name_expanded, *start_block)
.read_events(
&contract_address,
event_name_expanded,
*start_block,
to_block,
)
.await
.expect("Read a transfer event");
*start_block = to_block;
for event in events {
let EthereumDataType::Address(from) = event.values[0].clone() else {
panic!("wrong type for the first entry");
Expand Down
7 changes: 5 additions & 2 deletions examples/ethereum-tracker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,19 @@ impl ServiceAbi for EthereumTrackerAbi {
#[derive(Debug, Deserialize, Serialize, GraphQLMutationRoot)]
pub enum Operation {
/// Update the database by querying an Ethereum node
Update,
/// up to an exclusive block number
Update { to_block: u64 },
}

/// The initialization data required to create an Ethereum tracker
/// The instantiation argument used for the contract.
#[derive(Clone, Debug, Default, Deserialize, Serialize, SimpleObject)]
pub struct InstantiationArgument {
/// The Ethereum endpoint being used
pub ethereum_endpoint: String,
/// The address of the contract
pub contract_address: String,
/// The block height at which the EVM contract was created
pub start_block: u64,
}

#[derive(Clone, Default, Deserialize, Serialize)]
Expand Down
7 changes: 4 additions & 3 deletions examples/ethereum-tracker/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use ethereum_tracker::{InstantiationArgument, U256Cont};
use ethereum_tracker::U256Cont;
use linera_sdk::views::{linera_views, MapView, RegisterView, RootView, ViewStorageContext};

/// The application state.
#[derive(RootView, async_graphql::SimpleObject)]
#[view(context = "ViewStorageContext")]
pub struct EthereumTracker {
pub argument: RegisterView<InstantiationArgument>,
pub last_block: RegisterView<u64>,
pub ethereum_endpoint: RegisterView<String>,
pub contract_address: RegisterView<String>,
pub start_block: RegisterView<u64>,
pub accounts: MapView<String, U256Cont>,
}
12 changes: 9 additions & 3 deletions linera-ethereum/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,15 @@ pub trait EthereumQueries {
/// This is done from a specified `contract_address` and `event_name_expanded`.
/// That is one should have "MyEvent(type1 indexed,type2)" instead
/// of the usual "MyEvent(type1,type2)"
///
/// The `from_block` is inclusive.
/// The `to_block` is exclusive (contrary to Ethereum where it is inclusive)
async fn read_events(
&self,
contract_address: &str,
event_name_expanded: &str,
starting_block: u64,
from_block: u64,
to_block: u64,
) -> Result<Vec<EthereumEvent>, Self::Error>;

/// The operation done with `eth_call` on Ethereum returns
Expand Down Expand Up @@ -164,14 +168,16 @@ where
&self,
contract_address: &str,
event_name_expanded: &str,
starting_block: u64,
from_block: u64,
to_block: u64,
) -> Result<Vec<EthereumEvent>, Self::Error> {
let contract_address = contract_address.parse::<Address>()?;
let event_name = event_name_from_expanded(event_name_expanded);
let filter = Filter::new()
.address(contract_address)
.event(&event_name)
.from_block(starting_block);
.from_block(from_block)
.to_block(to_block - 1);
let events = self
.request::<_, Vec<Log>>("eth_getLogs", (filter,))
.await?;
Expand Down
3 changes: 3 additions & 0 deletions linera-ethereum/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum EthereumServiceError {
AlloyReqwestError(#[from] alloy::transports::http::reqwest::Error),
}

/// A single primitive data type. This is used for example for the
/// entries of Ethereum events.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum EthereumDataType {
Address(String),
Expand Down Expand Up @@ -171,6 +173,7 @@ fn parse_entry(entry: B256, ethereum_type: &str) -> Result<EthereumDataType, Eth
Err(EthereumServiceError::UnsupportedEthereumTypeError)
}

/// The data type for an Ethereum event emitted by a smart contract
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EthereumEvent {
pub values: Vec<EthereumDataType>,
Expand Down
7 changes: 5 additions & 2 deletions linera-ethereum/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ impl EthereumClientSimplified {
}
}

#[derive(Clone)]
pub struct EthereumClient<M> {
pub provider: M,
}
Expand Down Expand Up @@ -98,14 +99,16 @@ impl EthereumQueries for EthereumClient<HttpProvider> {
&self,
contract_address: &str,
event_name_expanded: &str,
starting_block: u64,
from_block: u64,
to_block: u64,
) -> Result<Vec<EthereumEvent>, EthereumServiceError> {
let contract_address = contract_address.parse::<Address>()?;
let event_name = event_name_from_expanded(event_name_expanded);
let filter = Filter::new()
.address(contract_address)
.event(&event_name)
.from_block(starting_block);
.from_block(from_block)
.to_block(to_block - 1);
let events = self.provider.get_logs(&filter).await?;
events
.into_iter()
Expand Down
12 changes: 8 additions & 4 deletions linera-ethereum/tests/ethereum_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ async fn test_event_numerics() -> anyhow::Result<()> {

// Test the conversion of the types
let event_name_expanded = "Types(address indexed,address,uint256,uint64,int64,uint32,int32,uint16,int16,uint8,int8,bool)";
let from_block = 0;
let to_block = 2;
let events = event_numerics
.anvil_test
.ethereum_client
.read_events(&contract_address, event_name_expanded, 0)
.read_events(&contract_address, event_name_expanded, from_block, to_block)
.await?;
let addr0 = event_numerics.anvil_test.get_address(0);
let big_value = U256::from_str("239675476885367459284564394732743434463843674346373355625")?;
Expand All @@ -83,7 +85,7 @@ async fn test_event_numerics() -> anyhow::Result<()> {
};
assert_eq!(*events, [target_event.clone()]);
let events = ethereum_client_simp
.read_events(&contract_address, event_name_expanded, 0)
.read_events(&contract_address, event_name_expanded, from_block, to_block)
.await?;
assert_eq!(*events, [target_event]);
Ok(())
Expand All @@ -106,13 +108,15 @@ async fn test_simple_token_events() -> anyhow::Result<()> {
// mining.
let value = U256::from(10);
simple_token.transfer(&addr0, &addr1, value).await?;
let from_block = 0;
let to_block = 3;

// Test the Transfer entries
let event_name_expanded = "Transfer(address indexed,address indexed,uint256)";
let events = simple_token
.anvil_test
.ethereum_client
.read_events(&contract_address, event_name_expanded, 0)
.read_events(&contract_address, event_name_expanded, from_block, to_block)
.await?;
let value = U256::from(10);
let target_event = EthereumEvent {
Expand All @@ -126,7 +130,7 @@ async fn test_simple_token_events() -> anyhow::Result<()> {
assert_eq!(*events, [target_event.clone()]);
// Using the simplified client
let events = ethereum_client_simp
.read_events(&contract_address, event_name_expanded, 0)
.read_events(&contract_address, event_name_expanded, from_block, to_block)
.await?;
assert_eq!(*events, [target_event]);
Ok(())
Expand Down
8 changes: 4 additions & 4 deletions linera-sdk/src/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use std::fmt::Debug;

use async_graphql::scalar;
use async_trait::async_trait;
/// The Ethereum type for a single event
pub use linera_ethereum::common::EthereumDataType;
/// The Ethereum type for an event
pub use linera_ethereum::common::EthereumEvent;
pub use linera_ethereum::{
client::EthereumQueries,
common::{EthereumDataType, EthereumEvent},
};
use linera_ethereum::{client::JsonRpcClient, common::EthereumServiceError};
use serde::{Deserialize, Serialize};

Expand Down
28 changes: 21 additions & 7 deletions linera-service/tests/end_to_end_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ impl EthereumTrackerApp {
}
}

async fn update(&self) {
self.0.mutate("update").await.unwrap();
async fn update(&self, to_block: u64) {
let mutation = format!("update(toBlock: {})", to_block);
self.0.mutate(mutation).await.unwrap();
}
}

Expand Down Expand Up @@ -361,25 +362,37 @@ async fn test_wallet_lock() -> Result<()> {
#[cfg_attr(feature = "scylladb", test_case(LocalNetConfig::new_test(Database::ScyllaDb, Network::Grpc) ; "scylladb_grpc"))]
#[cfg_attr(feature = "dynamodb", test_case(LocalNetConfig::new_test(Database::DynamoDb, Network::Grpc) ; "aws_grpc"))]
#[test_log::test(tokio::test)]
async fn test_wasm_end_to_end_ethereum_tracker(_config: impl LineraNetConfig) -> Result<()> {
async fn test_wasm_end_to_end_ethereum_tracker(config: impl LineraNetConfig) -> Result<()> {
use alloy::primitives::U256;
use ethereum_tracker::{EthereumTrackerAbi, InstantiationArgument};
use linera_ethereum::test_utils::{get_anvil, SimpleTokenContractFunction};
use linera_ethereum::{
client::EthereumQueries,
test_utils::{get_anvil, SimpleTokenContractFunction},
};
let _guard = INTEGRATION_TEST_GUARD.lock().await;

// Setting up the Ethereum smart contract
let anvil_test = get_anvil().await?;
let address0 = anvil_test.get_address(0);
let address1 = anvil_test.get_address(1);
let ethereum_endpoint = anvil_test.endpoint.clone();
let ethereum_client = anvil_test.ethereum_client.clone();

let simple_token = SimpleTokenContractFunction::new(anvil_test).await?;
let contract_address = simple_token.contract_address.clone();
let event_name_expanded = "Initial(address,uint256)";
let events = ethereum_client
.read_events(&contract_address, event_name_expanded, 0, 2)
.await?;
let start_block = events.first().unwrap().block_number;
let argument = InstantiationArgument {
ethereum_endpoint,
contract_address,
start_block,
};

let (_net, client) = _config.instantiate().await?;
// Setting up the validators
let (_net, client) = config.instantiate().await?;
let chain = client.load_wallet()?.default_chain().unwrap();

// Change the ownership so that the blocks inserted are not
Expand Down Expand Up @@ -426,8 +439,9 @@ async fn test_wasm_end_to_end_ethereum_tracker(_config: impl LineraNetConfig) ->

let value = U256::from(10);
simple_token.transfer(&address0, &address1, value).await?;

app.update().await;
let last_block = ethereum_client.get_block_number().await?;
// increment by 1 since the read_events is exclusive in the last block.
app.update(last_block + 1).await;

// Now checking the balances after the operations.

Expand Down

0 comments on commit de04d31

Please sign in to comment.