-
Notifications
You must be signed in to change notification settings - Fork 373
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
Use service to query ethereum node in Ethereum Tracker example #3144
base: main
Are you sure you want to change the base?
Conversation
Prepare to add custom queries beyond the ones derived from `SimpleObject`, in a way that has access to the `EthereumTrackerService` type.
Read the Ethereum event and return a processed response.
Have one to use in contracts and one to use in services.
Make it easier to convert to the custom type.
Avoid non-deterministic HTTP responses.
Read the Ethereum events and return a processed response.
Avoid non-deterministic HTTP responses.
It's no longer used by the contract because now it's the service that communicates with the Ethereum node.
Improve the interface when it becomes possible.
let async_graphql::Value::Object(data_object) = response.data else { | ||
panic!("Unexpected response from `readTransferEvents`: {response:#?}"); | ||
}; | ||
let async_graphql::Value::List(ref events) = data_object["readTransferEvents"] else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the "ref variable" something that we want to avoid?
let request = async_graphql::Request::new(format!( | ||
r#"query {{ readTransferEvents(endBlock: {end_block}) }}"# | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code in main is reading events from a start_block
to a to_block
(renamed end_block
here).
In the new code, there isn't anymore a start_code
. How do we avoid repeating the operations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service has access to the state, too, and read_transfer_events
uses the start_block
field. No need to pass it in here.
let value = U256::from_str_radix(&value_string[2..], 16) | ||
.expect("Balance could not be parsed as a hex string"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a little unfortunate to have this rather low level conversion occurring in this part of the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd imagine that FromStr
would work on the whole string here, and interpret it as hexadecimal because it starts with 0x
? I.e. maybe we can just use value_string.parse::<U256>()
?
async fn read_transfer_events(&self, end_block: u64) -> Vec<TransferEvent> { | ||
let start_block = *self.0.state.start_block.get(); | ||
let events = self | ||
.read_events( | ||
"Transfer(address indexed,address indexed,uint256)", | ||
start_block, | ||
end_block, | ||
) | ||
.await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I find a little odd is that now the code has two ways to access the events.
Here, we have the read_transfer_events
and in fn update
a different code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But update
is using read_transfer_events
. How else could we move this functionality into the service and use it from the contract?
self.state | ||
.save() | ||
.await | ||
.expect("Failed to write updated storage"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is exactly the justification for adding this self.state.save()
here? This is the only smart contract where a self.state.save()
is added to the fn instantiate
function. Why is that so?
For example, the crowd-funding
and fungible
are doing meaningful operations in their fn instantiate
functions. Why haven't they some self.state.save()
.
Side question 1: When is the fn store
function used?
Side question 2: Now, the fn save
function is doing the writing to the batch and if the batch is empty, then nothing is done. So, could the save
could be added by the linera-rpc?
let application_id = self.runtime.application_id(); | ||
let response = self.runtime.query_service(application_id, request); | ||
|
||
let async_graphql::Value::Object(data_object) = response.data else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could avoid all those async_graphql::Value::Variant
matches by using response.data.into_json()
instead. The JSON Value
type has a few more convenient methods like as_str
etc. (And we'd rarely need as_object
because e.g. json_data["readInitialEvent"]["address"]
works, too.)
(Anyway, maybe that's outside the scope of this PR.)
let value = U256::from_str_radix(&value_string[2..], 16) | ||
.expect("Balance could not be parsed as a hex string"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd imagine that FromStr
would work on the whole string here, and interpret it as hexadecimal because it starts with 0x
? I.e. maybe we can just use value_string.parse::<U256>()
?
async fn read_transfer_events(&self, end_block: u64) -> Vec<TransferEvent> { | ||
let start_block = *self.0.state.start_block.get(); | ||
let events = self | ||
.read_events( | ||
"Transfer(address indexed,address indexed,uint256)", | ||
start_block, | ||
end_block, | ||
) | ||
.await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But update
is using read_transfer_events
. How else could we move this functionality into the service and use it from the contract?
let request = async_graphql::Request::new(format!( | ||
r#"query {{ readTransferEvents(endBlock: {end_block}) }}"# | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service has access to the state, too, and read_transfer_events
uses the start_block
field. No need to pass it in here.
events | ||
.into_iter() | ||
.map(|event| { | ||
let EthereumDataType::Address(source) = event.values[0].clone() else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We aren't using event
anymore after this, so we might as well use event.values.into_iter()
instead of cloning the values.
@@ -5,7 +5,7 @@ use ethereum_tracker::U256Cont; | |||
use linera_sdk::views::{linera_views, MapView, RegisterView, RootView, ViewStorageContext}; | |||
|
|||
/// The application state. | |||
#[derive(RootView, async_graphql::SimpleObject)] | |||
#[derive(RootView)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, we could use ComplexObject
and remove the manual implementations of the four getters.
use crate::contract::wit::contract_system_api; | ||
use crate::{contract::wit::contract_system_api, service::wit::service_system_api}; | ||
|
||
// TODO(#3143): Unify the two types into a single `EthereumClient` type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the Contract
one still make sense at all? With the planned change for the HTTP oracle, I imagine it wouldn't work anymore?
Motivation
While working on PR #2631, the end-to-end test for the Ethereum Tracker example started failing. That happened because the updated HTTP request API also included the response headers in the oracle recording, and a "Date" header made the response non-deterministic.
Proposal
Use the service to perform the HTTP request, and only send back to the contract the actual deterministic information that matters.
In order to do so, the
EthereumClient
had to be split in two, so that it could use either the contract system APIs or the service system APIs.Test Plan
The end-to-end test passes locally in my repository when this fix is applied to the changes in PR #2631.
Release Plan
Links