Skip to content

Commit

Permalink
remote evm state upgrade (#109)
Browse files Browse the repository at this point in the history
* remote evm state upgrade
* compatiable bitfinity
* update admin check and add query interface
  • Loading branch information
StewartYe authored Aug 7, 2024
1 parent 4b74551 commit 32a1cfb
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 28 deletions.
6 changes: 6 additions & 0 deletions dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@
"package": "evm_route",
"type": "custom"
},
"evm_route_bitfinity": {
"candid": "route/evm/evm_route.did",
"wasm": "route/evm/target/wasm32-unknown-unknown/release/evm_route.wasm",
"package": "evm_route",
"type": "custom"
},
"evm_route_bitlayer": {
"candid": "route/evm/evm_route.did",
"wasm": "route/evm/target/wasm32-unknown-unknown/release/evm_route.wasm",
Expand Down
2 changes: 2 additions & 0 deletions route/evm/evm_route.did
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,13 @@ service : (InitArgs) -> {
get_ticket : (text) -> (opt record { nat64; Ticket }) query;
get_token_list : () -> (vec TokenResp) query;
http_request : (HttpRequest) -> (HttpResponse) query;
insert_pending_hash : (text) -> ();
metrics : () -> (MetricsStatus);
mint_token_status : (text) -> (MintTokenStatus) query;
pubkey_and_evm_addr : () -> (text, text);
query_directives : (nat64, nat64) -> (vec record { nat64; Directive }) query;
query_handled_event : (text) -> (opt text);
query_hub_tickets : (nat64) -> (vec record { nat64; Ticket });
query_pending_directive : (nat64, nat64) -> (
vec record { nat64; PendingDirectiveStatus },
) query;
Expand Down
60 changes: 58 additions & 2 deletions route/evm/src/eth_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,70 @@ use candid::{CandidType, Nat};
use cketh_common::eth_rpc::{Hash, RpcError};
use cketh_common::eth_rpc_client::providers::RpcService;
use cketh_common::eth_rpc_client::RpcConfig;
use cketh_common::numeric::BlockNumber;
use ethereum_types::Address;
use ethers_core::abi::ethereum_types;
use ethers_core::types::{Eip1559TransactionRequest, TransactionRequest, U256};
use ethers_core::utils::keccak256;
use evm_rpc::candid_types::{BlockTag, GetTransactionCountArgs, SendRawTransactionStatus};
use evm_rpc::{MultiRpcResult, RpcServices};
use evm_rpc::candid_types::{BlockTag, GetTransactionCountArgs, SendRawTransactionStatus};
use ic_cdk::api::management_canister::ecdsa::{sign_with_ecdsa, SignWithEcdsaArgument};
use log::{error, info};
use num_traits::ToPrimitive;
use serde_derive::{Deserialize, Serialize};

use crate::{Error, state};
use crate::const_args::{
BROADCAST_TX_CYCLES, EVM_ADDR_BYTES_LEN, EVM_FINALIZED_CONFIRM_HEIGHT, GET_ACCOUNT_NONCE_CYCLES,
};
use crate::eth_common::EvmAddressError::LengthError;
use crate::{state, Error};

#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct TransactionReceipt {
#[serde(rename = "blockHash")]
pub block_hash: String,
#[serde(rename = "blockNumber")]
pub block_number: String,
#[serde(rename = "gasUsed")]
pub gas_used: String,
pub status: String,
#[serde(rename = "transactionHash")]
pub transaction_hash: String,
#[serde(rename = "contractAddress")]
pub contract_address: Option<String>,
pub from: String,
pub logs: Vec<cketh_common::eth_rpc::LogEntry>,
#[serde(rename = "logsBloom")]
pub logs_bloom: String,
pub to: String,
#[serde(rename = "transactionIndex")]
pub transaction_index: String,
pub r#type: String,
}

impl Into<evm_rpc::candid_types::TransactionReceipt> for TransactionReceipt {
fn into(self) -> evm_rpc::candid_types::TransactionReceipt {
evm_rpc::candid_types::TransactionReceipt {
block_hash: self.block_hash,
block_number: BlockNumber::new(hex_to_u64(&self.block_number) as u128),
effective_gas_price: Default::default(),
gas_used: hex_to_u64(&self.gas_used).into(),
status: hex_to_u64(&self.status).into(),
transaction_hash: self.transaction_hash,
contract_address: self.contract_address,
from: self.from,
logs: self.logs,
logs_bloom: self.logs_bloom,
to: self.to,
transaction_index: hex_to_u64(&self.transaction_index).into(),
r#type: self.r#type,
}
}
}

pub fn hex_to_u64(hex_str: &String) -> u64 {
u64::from_str_radix(hex_str.strip_prefix("0x").unwrap(), 16).unwrap()
}

#[derive(Deserialize, CandidType, Serialize, Default, Clone, Eq, PartialEq)]
pub struct EvmAddress(pub(crate) [u8; EVM_ADDR_BYTES_LEN]);
Expand Down Expand Up @@ -393,3 +441,11 @@ pub struct EvmJsonRpcRequest {
pub id: u64,
pub jsonrpc: String,
}


#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct JsonRpcResponse<T> {
pub jsonrpc: String,
pub result: T,
pub id: u32,
}
46 changes: 37 additions & 9 deletions route/evm/src/evm_scan.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
use anyhow::anyhow;
use cketh_common::eth_rpc::Hash;
use cketh_common::{eth_rpc::LogEntry, eth_rpc_client::RpcConfig};
use cketh_common::eth_rpc::{Hash, HttpOutcallError, RpcError};
use ethers_core::abi::RawLog;
use ethers_core::utils::hex::ToHexExt;
use evm_rpc::candid_types::TransactionReceipt;
use evm_rpc::{MultiRpcResult, RpcServices};
use evm_rpc::candid_types::TransactionReceipt;
use itertools::Itertools;
use log::{error, info};

use crate::*;
use crate::const_args::{SCAN_EVM_CYCLES, SCAN_EVM_TASK_NAME};
use crate::contract_types::{
AbiSignature, DecodeLog, DirectiveExecuted, RunesMintRequested, TokenAdded, TokenBurned,
TokenMinted, TokenTransportRequested,
AbiSignature, DecodeLog, DirectiveExecuted, RunesMintRequested, TokenAdded,
TokenBurned, TokenMinted, TokenTransportRequested,
};
use crate::eth_common::JsonRpcResponse;
use crate::state::{mutate_state, read_state};
use crate::types::{ChainState, Directive, Ticket};
use crate::*;

pub fn scan_evm_task() {
ic_cdk::spawn(async {
Expand Down Expand Up @@ -224,10 +225,37 @@ pub async fn get_transaction_receipt(
.await
.map_err(|err| Error::IcCallError(err.0, err.1))?;
match rpc_result {
MultiRpcResult::Consistent(result) => result.map_err(|e| {
error!("query transaction receipt error: {:?}", e.clone());
Error::EvmRpcError(format!("{:?}", e))
}),
MultiRpcResult::Consistent(result) => match result {
Ok(info) => {
return Ok(info);
}
Err(e) => {
if let RpcError::HttpOutcallError(ee) = e.clone() {
match ee {
HttpOutcallError::IcError { .. } => {}
HttpOutcallError::InvalidHttpJsonRpcResponse {
status,
body,
..
} => {
if status == 200 {
info!("content: {}", &body);
let json_rpc: JsonRpcResponse<eth_common::TransactionReceipt> =
serde_json::from_str(&body).map_err(|e| {
Error::EvmRpcError(format!(
"local deserialize error: {}",
e.to_string()
))
})?;
return Ok(Some(json_rpc.result.into()));
}
}
}
}
error!("query transaction receipt error: {:?}", e.clone());
Err(Error::EvmRpcError(format!("{:?}", e)))
}
},
MultiRpcResult::Inconsistent(_) => {
Err(super::Error::EvmRpcError("Inconsistent result".to_string()))
}
Expand Down
4 changes: 2 additions & 2 deletions route/evm/src/hub.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use candid::utils::ArgumentEncoder;
use candid::{CandidType, Principal};
use candid::utils::ArgumentEncoder;

use crate::call_error::{CallError, Reason};
use crate::types::Topic;
use crate::types::{ChainId, Directive, TicketId};
use crate::types::{Seq, Ticket};
use crate::types::Topic;

pub async fn send_ticket(hub_principal: Principal, ticket: Ticket) -> Result<(), CallError> {
call(hub_principal, "send_ticket".into(), (ticket,)).await
Expand Down
36 changes: 26 additions & 10 deletions route/evm/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,22 @@ use ic_cdk_timers::set_timer_interval;
use log::{error, info};
use serde_derive::Deserialize;

use crate::const_args::{
FETCH_HUB_DIRECTIVE_INTERVAL, FETCH_HUB_TICKET_INTERVAL, MONITOR_PRINCIPAL,
SCAN_EVM_TASK_INTERVAL, SEND_EVM_TASK_INTERVAL,
};
use crate::eth_common::{get_balance, EvmAddress, EvmTxType};
use crate::{Error, get_time_secs, hub};
use crate::const_args::{BATCH_QUERY_LIMIT, FETCH_HUB_DIRECTIVE_INTERVAL, FETCH_HUB_TICKET_INTERVAL, MONITOR_PRINCIPAL, SCAN_EVM_TASK_INTERVAL, SEND_EVM_TASK_INTERVAL};
use crate::eth_common::{EvmAddress, EvmTxType, get_balance};
use crate::evm_scan::{create_ticket_by_tx, scan_evm_task};
use crate::hub_to_route::{fetch_hub_directive_task, fetch_hub_ticket_task};
use crate::route_to_evm::{send_directive, send_ticket, to_evm_task};
use crate::stable_log::{init_log, StableLogWriter};
use crate::stable_memory::init_stable_log;
use crate::state::{
init_chain_pubkey, minter_addr, mutate_state, read_state, replace_state, EvmRouteState,
EvmRouteState, init_chain_pubkey, minter_addr, mutate_state, read_state, replace_state,
StateProfile,
};
use crate::types::{
Chain, ChainId, Directive, MetricsStatus, MintTokenStatus, Network, PendingDirectiveStatus,
PendingTicketStatus, Seq, Ticket, TicketId, TokenResp,
};
use crate::{get_time_secs, hub, Error};

#[init]
fn init(args: InitArgs) {
Expand All @@ -42,8 +39,8 @@ fn pre_upgrade() {
}

#[post_upgrade]
fn post_upgrade(block_interval_sec: u64) {
EvmRouteState::post_upgrade(block_interval_sec);
fn post_upgrade() {
EvmRouteState::post_upgrade();
init_log(Some(init_stable_log()));
start_tasks();
info!("[evmroute] upgraded successed at {}", ic_cdk::api::time());
Expand Down Expand Up @@ -209,7 +206,7 @@ fn update_rpcs(rpcs: Vec<RpcApi>) {

fn is_admin() -> Result<(), String> {
let c = ic_cdk::caller();
match read_state(|s| s.admins.contains(&c)) {
match ic_cdk::api::is_controller(&c) || read_state(|s| s.admins.contains(&c)) {
true => Ok(()),
false => Err("permission deny".to_string()),
}
Expand Down Expand Up @@ -261,6 +258,25 @@ async fn generate_ticket(hash: String) -> Result<(), String> {
Ok(())
}

#[update(guard = "is_admin")]
pub fn insert_pending_hash(tx_hash: String) {
mutate_state(|s| s.pending_events_on_chain.insert(tx_hash, get_time_secs()));
}

#[update(guard = "is_admin")]
pub async fn query_hub_tickets(start: u64) -> Vec<(Seq, Ticket)> {
let hub_principal = read_state(|s| s.hub_principal);
match hub::query_tickets(hub_principal, start, BATCH_QUERY_LIMIT).await {
Ok(tickets) => {
return tickets
}
Err(err) => {
log::error!("[process tickets] failed to query tickets, err: {}", err);
return vec![];
}
}
}

#[update(guard = "is_admin")]
pub fn query_handled_event(tx_hash: String) -> Option<String> {
read_state(|s| s.handled_evm_event.get(&tx_hash).cloned())
Expand Down
8 changes: 3 additions & 5 deletions route/evm/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use crate::types::{Chain, ChainState, Token, TokenId};
use crate::types::{
ChainId, Directive, PendingDirectiveStatus, PendingTicketStatus, Seq, Ticket, TicketId,
};
use crate::upgrade::OldEvmRouteState;
use crate::{stable_memory, Error};

thread_local! {
Expand Down Expand Up @@ -90,7 +89,7 @@ impl EvmRouteState {
.expect("failed to save hub state");
}

pub fn post_upgrade(bloc_interval_secs: u64) {
pub fn post_upgrade() {
use ic_stable_structures::Memory;
let memory = stable_memory::get_upgrade_stash_memory();
// Read the length of the state bytes.
Expand All @@ -99,10 +98,9 @@ impl EvmRouteState {
let state_len = u32::from_le_bytes(state_len_bytes) as usize;
let mut state_bytes = vec![0; state_len];
memory.read(4, &mut state_bytes);
let state: OldEvmRouteState =
let state: EvmRouteState =
ciborium::de::from_reader(&*state_bytes).expect("failed to decode state");
let new_state = EvmRouteState::from((state, bloc_interval_secs));
replace_state(new_state);
replace_state(state);
}

pub fn pull_tickets(&self, from: usize, limit: usize) -> Vec<(Seq, Ticket)> {
Expand Down

0 comments on commit 32a1cfb

Please sign in to comment.