diff --git a/Cargo.lock b/Cargo.lock index b394941d40..05cdc5e9d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -814,10 +814,13 @@ dependencies = [ "ethers", "futures", "hex", + "hubble", "prost", "protos", + "reqwest", "serde", "serde-utils", + "serde_json", "sha2 0.10.7", "tendermint-rpc", "tokio", @@ -4892,9 +4895,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -6239,6 +6242,7 @@ dependencies = [ "futures", "hex", "hex-literal", + "hubble", "num-bigint", "pin-utils", "prost", diff --git a/Cargo.toml b/Cargo.toml index 0dcfad91c1..eee22952ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ protos = { path = "generated/rust", default-features = false } unionlabs = { path = "lib/unionlabs", default-features = false } beacon-api = { path = "lib/beacon-api", default-features = false } chain-utils = { path = "lib/chain-utils", default-features = false } +hubble = { path = "hubble" } token-factory-api = { path = "cosmwasm/token-factory-api", default-features = false } ucs01-relay-api = { path = "cosmwasm/ucs01-relay-api", default-features = false } diff --git a/hubble/src/hasura.rs b/hubble/src/hasura.rs index 0be486c145..db8d47a1f6 100644 --- a/hubble/src/hasura.rs +++ b/hubble/src/hasura.rs @@ -14,13 +14,19 @@ pub trait Datastore { ::Variables: 'static; } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct HasuraDataStore { client: Client, url: Url, secret: String, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct HasuraConfig { + pub url: Url, + pub secret: String, +} + impl Datastore for HasuraDataStore { /// Performs a GraphQL post request (which may query or mutate). /// It injects the x-hasura-admin-secret header. diff --git a/lib/chain-utils/Cargo.toml b/lib/chain-utils/Cargo.toml index 2953b33ee3..cecccfa9a9 100644 --- a/lib/chain-utils/Cargo.toml +++ b/lib/chain-utils/Cargo.toml @@ -25,3 +25,6 @@ typenum = { version = "1.16.0", default-features = false, features = ["const-gen prost = "*" sha2 = "0.10.6" chrono = { version = "0.4.26", default-features = false, features = ["alloc"] } +hubble.workspace = true +reqwest = "0.11.20" +serde_json = "1.0.107" diff --git a/lib/chain-utils/src/evm.rs b/lib/chain-utils/src/evm.rs index 38e071535e..ff09effae4 100644 --- a/lib/chain-utils/src/evm.rs +++ b/lib/chain-utils/src/evm.rs @@ -19,12 +19,13 @@ use ethers::{ signers::{LocalWallet, Wallet}, utils::secret_key_to_address, }; -use futures::{stream, Future, FutureExt, Stream, StreamExt}; +use futures::{stream, Future, FutureExt, Stream, StreamExt, TryStreamExt}; +use hubble::hasura::{insert_demo_tx, Datastore, HasuraConfig, HasuraDataStore, InsertDemoTx}; use serde::{Deserialize, Serialize}; use typenum::Unsigned; use unionlabs::{ ethereum::{Address, H256, U256}, - ethereum_consts_traits::ChainSpec, + ethereum_consts_traits::{ChainSpec, Mainnet, Minimal}, events::{ AcknowledgePacket, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, ChannelOpenTry, ConnectionOpenAck, ConnectionOpenConfirm, ConnectionOpenInit, ConnectionOpenTry, @@ -57,6 +58,8 @@ pub struct Evm { pub ibc_handler: IBCHandler, pub provider: Provider, pub beacon_api_client: BeaconApiClient, + + pub hasura_client: HasuraDataStore, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -71,6 +74,8 @@ pub struct Config { pub eth_rpc_api: String, /// The RPC endpoint for the beacon chain. pub eth_beacon_rpc_api: String, + + pub hasura_config: HasuraConfig, } impl Chain for Evm { @@ -277,18 +282,24 @@ impl Evm { provider, beacon_api_client: BeaconApiClient::new(config.eth_beacon_rpc_api).await, wallet, + hasura_client: HasuraDataStore::new( + reqwest::Client::new(), + config.hasura_config.url, + config.hasura_config.secret, + ), } } // TODO: Change to take a beacon slot instead of a height - // TODO: Change to return a block number, not a height pub async fn execution_height(&self, beacon_height: Height) -> u64 { - let height = self + let response = self .beacon_api_client .block(beacon_api::client::BlockId::Slot( beacon_height.revision_height, )) - .await + .await; + + let height = response .unwrap() .data .message @@ -426,6 +437,14 @@ impl EventSource for Evm { _seed: Self::Seed, ) -> impl Stream> + '_ { async move { + let genesis_time = self + .beacon_api_client + .genesis() + .await + .unwrap() + .data + .genesis_time; + stream::unfold( self.query_latest_height().await, move |previous_beacon_height| async move { @@ -825,6 +844,26 @@ impl EventSource for Evm { }, ) .flatten() + .then(move |event| async move { + if let Ok(ref event) = event { + let current_slot = event.height.revision_height; + + let next_epoch_ts = next_epoch_timestamp::(current_slot, genesis_time); + + self.hasura_client + .do_post::(insert_demo_tx::Variables { + data: serde_json::json! {{ + "latest_execution_block_hash": event.block_hash, + "timestamp": next_epoch_ts, + }}, + }) + .await + .unwrap(); + } + + // pass it back through + event + }) } .flatten_stream() .inspect(|x| { @@ -904,3 +943,21 @@ impl_eth_call_ext! { GetChannelCall -> GetChannelReturn; GetHashedPacketCommitmentCall -> GetHashedPacketCommitmentReturn; } + +pub fn next_epoch_timestamp(slot: u64, genesis_timestamp: u64) -> u64 { + let next_epoch_slot = slot + (C::SLOTS_PER_EPOCH::U64 - (slot % C::SLOTS_PER_EPOCH::U64)); + genesis_timestamp + (next_epoch_slot * C::SECONDS_PER_SLOT::U64) +} + +#[test] +fn next_epoch_ts() { + dbg!(next_epoch_timestamp::(6, 0)); + dbg!(next_epoch_timestamp::(7, 0)); + dbg!(next_epoch_timestamp::(8, 0)); + dbg!(next_epoch_timestamp::(9, 0)); + + dbg!(next_epoch_timestamp::(6, 0)); + // dbg!(next_epoch::(48, 0)); + // dbg!(next_epoch::(49, 0)); + // dbg!(next_epoch::(47, 0)); +} diff --git a/voyager-config.json b/voyager-config.json index f796267f20..956f2fde92 100644 --- a/voyager-config.json +++ b/voyager-config.json @@ -9,7 +9,11 @@ "raw": "0x4e9444a6efd6d42725a250b650a781da2737ea308c839eaccb0f7f3dbd2fea77" }, "eth_rpc_api": "ws://localhost:8546", - "eth_beacon_rpc_api": "http://localhost:9596" + "eth_beacon_rpc_api": "http://localhost:9596", + "hasura_config": { + "url": "https://graphql.union.build/v1/graphql", + "secret": "3N5Mt2f4Y1AC7dE663AsGqRy66yiHBuZ3RMgUjM6X4Q3Ma8G2jihgfchsdasdsadasda" + } }, "union-devnet": { "chain_type": "union", diff --git a/voyager/Cargo.toml b/voyager/Cargo.toml index dd87a9b2bf..63abf83bab 100644 --- a/voyager/Cargo.toml +++ b/voyager/Cargo.toml @@ -47,6 +47,7 @@ frunk = "0.4.2" bitvec = "1.0.1" displaydoc = { version = "0.2.4", default-features = false } frame-support-procedural = "18.0.0" +hubble.workspace = true [features] -eth-mainnet = [ "unionlabs/eth-mainnet" ] \ No newline at end of file +eth-mainnet = [ "unionlabs/eth-mainnet" ] diff --git a/voyager/src/chain.rs b/voyager/src/chain.rs index 486511a552..f2c84bd016 100644 --- a/voyager/src/chain.rs +++ b/voyager/src/chain.rs @@ -45,6 +45,7 @@ impl AnyChain { signer: evm.signer, eth_rpc_api: evm.eth_rpc_api, eth_beacon_rpc_api: evm.eth_beacon_rpc_api, + hasura_config: evm.hasura_config, }) .await, ), @@ -54,6 +55,7 @@ impl AnyChain { signer: evm.signer, eth_rpc_api: evm.eth_rpc_api, eth_beacon_rpc_api: evm.eth_beacon_rpc_api, + hasura_config: evm.hasura_config, }) .await, ), diff --git a/voyager/src/chain/evm.rs b/voyager/src/chain/evm.rs index 91c8b3717b..8bc8a7f858 100644 --- a/voyager/src/chain/evm.rs +++ b/voyager/src/chain/evm.rs @@ -29,9 +29,11 @@ use ethers::{ use frame_support_procedural::{CloneNoBound, DebugNoBound, PartialEqNoBound}; use frunk::{hlist_pat, HList}; use futures::Future; +use hubble::hasura::{insert_demo_tx, Datastore, InsertDemoTx}; use prost::Message; use protos::union::ibc::lightclients::ethereum::v1 as ethereum_v1; use serde::{Deserialize, Serialize}; +use serde_json::json; use typenum::Unsigned; use unionlabs::{ ethereum::{ diff --git a/voyager/src/cli.rs b/voyager/src/cli.rs index 561da5faba..e6ef38501b 100644 --- a/voyager/src/cli.rs +++ b/voyager/src/cli.rs @@ -10,12 +10,10 @@ use ethers::{ types::{Address, H256}, }; use reqwest::Url; -use unionlabs::{ethereum_consts_traits::PresetBaseKind, ibc::core::client::height::Height}; +use unionlabs::ibc::core::client::height::Height; use crate::chain::{ - evm::CometblsConfig, proof::{self, ClientConsensusStatePath, ClientStatePath, IbcStateReadPaths}, - union::EthereumConfig, HeightOf, QueryHeight, }; @@ -38,14 +36,6 @@ pub struct AppArgs { #[allow(clippy::large_enum_variant)] pub enum Command { PrintConfig, - #[command(subcommand)] - Chain(ChainCmd), - #[command(subcommand)] - Client(ClientCmd), - #[command(subcommand)] - Connection(ConnectionCmd), - #[command(subcommand)] - Channel(ChannelCmd), Relay(RelayCmd), #[command(subcommand)] SubmitPacket(SubmitPacketCmd), @@ -194,148 +184,6 @@ pub enum QueryCmd { }, } -#[derive(Debug, Subcommand)] -pub enum ChainCmd { - #[command(subcommand)] - Add(ChainAddCmd), -} - -#[derive(Debug, Subcommand)] -pub enum ChainAddCmd { - Evm { - #[arg(long)] - overwrite: bool, - #[arg(long)] - name: String, - #[arg(long)] - preset_base: PresetBaseKind, - #[command(flatten)] - config: crate::config::EvmChainConfigFields, - }, - Union { - #[arg(long)] - overwrite: bool, - #[arg(long)] - name: String, - #[command(flatten)] - config: crate::config::UnionChainConfig, - }, -} - -#[derive(Debug, Subcommand)] -pub enum ClientCmd { - #[command(subcommand)] - Create(ClientCreateCmd), -} - -#[derive(Debug, Subcommand)] -pub enum ClientCreateCmd { - #[command(subcommand)] - Evm(EvmClientType), - #[command(subcommand)] - Union(CometblsClientType), -} - -#[derive(Debug, Args)] -pub struct ClientQueryCmd { - #[arg(long)] - pub client_id: String, - #[arg(long)] - pub on: String, -} - -#[derive(Debug, Subcommand)] -pub enum EvmClientType { - Cometbls { - /// The name of the chain to create the client on, as specified in the config file. - #[arg(long)] - on: String, - /// The name of the chain that the client will connect to, as specified in the config file. - #[arg(long)] - counterparty: String, - #[command(flatten)] - config: CometblsConfig, - }, -} - -#[derive(Debug, Subcommand)] -pub enum CometblsClientType { - Ethereum08Wasm { - /// The name of the chain to create the client on, as specified in the config file. - #[arg(long)] - on: String, - /// The name of the chain that the client will connect to, as specified in the config file. - #[arg(long)] - counterparty: String, - #[command(flatten)] - config: EthereumConfig, - }, -} - -#[derive(Debug, Subcommand)] -pub enum ConnectionCmd { - // #[command(subcommand)] - // Query(ConnectionQueryCmd), - Open { - #[arg(long)] - from_chain: String, - #[arg(long)] - from_client: String, - - #[arg(long)] - to_chain: String, - #[arg(long)] - to_client: String, - }, -} - -#[derive(Debug, Subcommand)] -pub enum ChannelCmd { - Open { - #[arg(long)] - from_chain: String, - #[arg(long)] - from_connection: String, - #[arg(long)] - from_port: String, - #[arg(long)] - from_version: String, - - #[arg(long)] - to_chain: String, - #[arg(long)] - to_connection: String, - #[arg(long)] - to_port: String, - #[arg(long)] - to_version: String, - }, -} - -#[derive(Debug, Parser)] -pub struct OpenConnectionArgs { - #[command(flatten)] - pub args: ClientArgs, -} - -#[derive(Debug, Parser)] -pub struct OpenChannelArgs { - #[command(flatten)] - pub args: ClientArgs, - - #[arg(long)] - pub cometbls_port_id: String, - #[arg(long)] - pub ethereum_port_id: String, - - /// format is client_id/connection_id - #[arg(long)] - pub cometbls: ConnectionEndInfo, - /// format is client_id/connection_id - #[arg(long)] - pub ethereum: ConnectionEndInfo, -} - #[derive(Debug, Parser)] pub struct RelayCmd { #[arg(long)] diff --git a/voyager/src/config.rs b/voyager/src/config.rs index 93c6948155..0a2578ac98 100644 --- a/voyager/src/config.rs +++ b/voyager/src/config.rs @@ -6,6 +6,7 @@ use clap::{ Args, }; use ethers::prelude::k256::ecdsa; +use hubble::hasura::HasuraConfig; use serde::{Deserialize, Serialize}; use tendermint_rpc::WebSocketClientUrl; use unionlabs::ethereum::Address; @@ -41,33 +42,25 @@ pub enum EvmChainConfig { Minimal(EvmChainConfigFields), } -#[derive(Debug, Clone, Serialize, Deserialize, Args)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct EvmChainConfigFields { // TODO: Move all except for ibc_handler into the client config? /// The address of the `CometblsClient` smart contract. - #[arg(long)] pub cometbls_client_address: Address, /// The address of the `IBCHandler` smart contract. - #[arg(long)] pub ibc_handler_address: Address, /// The signer that will be used to submit transactions by voyager. - #[arg( - long, - value_parser = StringValueParser::new() - .try_map(parse_private_key_arg::) - )] pub signer: PrivateKey, // TODO(benluelo): Use `Url` or something similar /// The RPC endpoint for the execution chain. - #[arg(long)] pub eth_rpc_api: String, /// The RPC endpoint for the beacon chain. - #[arg(long)] pub eth_beacon_rpc_api: String, // #[arg(long)] // pub wasm_code_id: H256, + pub hasura_config: HasuraConfig, } impl From for chain_utils::evm::Config { @@ -77,6 +70,7 @@ impl From for chain_utils::evm::Config { signer: value.signer, eth_rpc_api: value.eth_rpc_api, eth_beacon_rpc_api: value.eth_beacon_rpc_api, + hasura_config: value.hasura_config, } } } diff --git a/voyager/src/main.rs b/voyager/src/main.rs index b6aff90ba1..83d5589463 100644 --- a/voyager/src/main.rs +++ b/voyager/src/main.rs @@ -14,7 +14,8 @@ use std::fs::read_to_string; use chain_utils::{evm::Evm, union::Union}; use clap::Parser; -use unionlabs::ethereum_consts_traits::{Mainnet, Minimal}; +use typenum::Unsigned; +use unionlabs::ethereum_consts_traits::{ChainSpec, Mainnet, Minimal}; use crate::{ chain::AnyChain, diff --git a/voyager/src/queue.rs b/voyager/src/queue.rs index 2e314ecf86..f8fcc6eece 100644 --- a/voyager/src/queue.rs +++ b/voyager/src/queue.rs @@ -12,6 +12,7 @@ use chain_utils::{ }; use frunk::{hlist_pat, HList}; use futures::{future::BoxFuture, stream, FutureExt, StreamExt, TryStreamExt}; +use hubble::hasura::{Datastore, HasuraDataStore, InsertDemoTx}; use unionlabs::{ ethereum_consts_traits::{Mainnet, Minimal}, events::{ @@ -96,6 +97,8 @@ pub struct Voyager { HashMap<< as Chain>::SelfClientState as ClientState>::ChainId, Evm>, union: HashMap<<::SelfClientState as ClientState>::ChainId, Union>, msg_server: msg_server::MsgServer, + + hasura_config: hubble::hasura::HasuraDataStore, } impl Voyager { @@ -134,6 +137,11 @@ impl Voyager { evm_mainnet, union, msg_server: msg_server::MsgServer, + hasura_config: HasuraDataStore::new( + reqwest::Client::new(), + "https://graphql.union.build/v1/graphql".parse().unwrap(), + "3N5Mt2f4Y1AC7dE663AsGqRy66yiHBuZ3RMgUjM6X4Q3Ma8G2jihgfchsdasdsadasda".to_string(), + ), } } @@ -632,17 +640,28 @@ impl Voyager { async move { match msg { - RelayerMsg::Lc(AnyLcMsg::EthereumMainnet(msg)) => { - self.handle_msg_generic::(msg).await - } - RelayerMsg::Lc(AnyLcMsg::EthereumMinimal(msg)) => { - self.handle_msg_generic::(msg).await - } - RelayerMsg::Lc(AnyLcMsg::CometblsMainnet(msg)) => { - self.handle_msg_generic::(msg).await - } - RelayerMsg::Lc(AnyLcMsg::CometblsMinimal(msg)) => { - self.handle_msg_generic::(msg).await + RelayerMsg::Lc(any_lc_msg) => { + self.hasura_config + .do_post::(hubble::hasura::insert_demo_tx::Variables { + data: serde_json::to_value(&any_lc_msg).unwrap(), + }) + .await + .unwrap(); + + match any_lc_msg { + AnyLcMsg::EthereumMainnet(msg) => { + self.handle_msg_generic::(msg).await + } + AnyLcMsg::EthereumMinimal(msg) => { + self.handle_msg_generic::(msg).await + } + AnyLcMsg::CometblsMainnet(msg) => { + self.handle_msg_generic::(msg).await + } + AnyLcMsg::CometblsMinimal(msg) => { + self.handle_msg_generic::(msg).await + } + } } RelayerMsg::Chain(AnyChainMsg::EvmMinimal(msg)) => {