diff --git a/Cargo.lock b/Cargo.lock index 25a9583bc5..f85950d757 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2461,7 +2461,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6ee87af31d84ef885378aebca32be3d682b0e0dc119d5b4860a2c5bb5046730" dependencies = [ - "uuid 0.8.2", + "uuid", ] [[package]] @@ -2975,7 +2975,7 @@ dependencies = [ "sha2 0.10.8", "sha3", "thiserror", - "uuid 0.8.2", + "uuid", ] [[package]] @@ -6793,7 +6793,7 @@ checksum = "13f4d162ecaaa1467de5afbe62d597757b674b51da8bb4e587430c5fdb2af7aa" dependencies = [ "fallible-iterator", "scroll 0.10.2", - "uuid 0.8.2", + "uuid", ] [[package]] @@ -8352,46 +8352,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -[[package]] -name = "sentinel" -version = "0.1.0" -dependencies = [ - "bech32", - "chain-utils", - "chrono", - "clap 4.5.4", - "contracts", - "cosmwasm-std 2.0.3", - "csv", - "dashmap", - "derive_more", - "ethers", - "futures", - "hex", - "parking_lot 0.11.2", - "prost 0.12.6", - "protos", - "queue-msg", - "rand 0.8.5", - "reqwest", - "serde", - "serde-utils", - "serde_json", - "sqlx", - "tendermint", - "tendermint-proto", - "tendermint-rpc", - "tokio", - "tokio-stream", - "tonic 0.10.2", - "tracing", - "tracing-subscriber", - "ucs01-relay", - "ucs01-relay-api", - "unionlabs", - "uuid 1.8.0", -] - [[package]] name = "serde" version = "1.0.203" @@ -8872,7 +8832,6 @@ dependencies = [ "bigdecimal", "byteorder", "bytes", - "chrono", "crc", "crossbeam-queue", "either", @@ -8957,7 +8916,6 @@ dependencies = [ "bitflags 2.5.0", "byteorder", "bytes", - "chrono", "crc", "digest 0.10.7", "dotenvy", @@ -9001,7 +8959,6 @@ dependencies = [ "bigdecimal", "bitflags 2.5.0", "byteorder", - "chrono", "crc", "dotenvy", "etcetera", @@ -9039,7 +8996,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", - "chrono", "flume", "futures-channel", "futures-core", @@ -9293,7 +9249,7 @@ dependencies = [ "debugid", "memmap2", "stable_deref_trait", - "uuid 0.8.2", + "uuid", ] [[package]] @@ -9528,7 +9484,7 @@ dependencies = [ "tokio", "tracing", "url", - "uuid 0.8.2", + "uuid", "walkdir", ] @@ -10676,16 +10632,6 @@ dependencies = [ "serde", ] -[[package]] -name = "uuid" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" -dependencies = [ - "getrandom 0.2.12", - "serde", -] - [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index d08e363586..1a146d4579 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,6 @@ members = [ "near/near-light-client", "near/dummy-ibc-app", "near/near-ibc-tests", - "sentinel", ] [workspace.package] @@ -125,7 +124,6 @@ ssz-derive = { path = "lib/ssz-derive", default-features = false tendermint-light-client = { path = "light-clients/tendermint-light-client", default-features = false } tendermint-verifier = { path = "lib/tendermint-verifier", default-features = false } token-factory-api = { path = "cosmwasm/token-factory-api", default-features = false } -ucs01-relay = { path = "cosmwasm/ucs01-relay", default-features = false } ucs01-relay-api = { path = "cosmwasm/ucs01-relay-api", default-features = false } unionlabs = { path = "lib/unionlabs", default-features = false } voyager-message = { path = "lib/voyager-message", default-features = false } diff --git a/flake.nix b/flake.nix index f7526b832a..9402d1eb88 100644 --- a/flake.nix +++ b/flake.nix @@ -256,7 +256,6 @@ ./devnet-compose/devnet-compose.nix ./faucet/faucet.nix ./ucli/ucli.nix - ./sentinel/sentinel.nix treefmt-nix.flakeModule ]; diff --git a/lib/block-message/src/chain/ethereum.rs b/lib/block-message/src/chain/ethereum.rs index ba5e1003d4..ebf40ec511 100644 --- a/lib/block-message/src/chain/ethereum.rs +++ b/lib/block-message/src/chain/ethereum.rs @@ -43,7 +43,7 @@ use unionlabs::{ }, ics24::ChannelEndPath, id::ClientIdValidator, - traits::{ChainIdOf, ClientIdOf, HeightOf}, + traits::{Chain, ChainIdOf, ClientIdOf, HeightOf}, validated::ValidateT, }; @@ -54,9 +54,7 @@ use crate::{ id, AnyChainIdentified, BlockMessage, ChainExt, DoAggregate, Identified, IsAggregateData, }; -pub trait EthereumChainExt = ChainExt - + chain_utils::ethereum::EthereumExecutionRpcsExt - + chain_utils::ethereum::EthereumChain; +pub trait EthereumChainExt = ChainExt + chain_utils::ethereum::EthereumChainExt; impl ChainExt for Ethereum { type Data = EthereumData; diff --git a/lib/chain-utils/src/arbitrum.rs b/lib/chain-utils/src/arbitrum.rs index c72c020cad..2d91cf7c24 100644 --- a/lib/chain-utils/src/arbitrum.rs +++ b/lib/chain-utils/src/arbitrum.rs @@ -26,8 +26,8 @@ use unionlabs::{ use crate::{ ethereum::{ self, balance_of_signers, get_proof, Ethereum, EthereumChain, EthereumConsensusChain, - EthereumExecutionRpcs, EthereumInitError, EthereumKeyring, EthereumSignerMiddleware, - EthereumSignersConfig, ReadWrite, Readonly, + EthereumInitError, EthereumKeyring, EthereumSignerMiddleware, EthereumSignersConfig, + ReadWrite, Readonly, }, keyring::{ChainKeyring, ConcurrentKeyring, KeyringConfig, SignerBalance}, union::Union, @@ -155,7 +155,7 @@ impl Arbitrum { } } -impl EthereumExecutionRpcs for Arbitrum { +impl EthereumChain for Arbitrum { fn provider(&self) -> Arc> { self.provider.clone() } diff --git a/lib/chain-utils/src/berachain.rs b/lib/chain-utils/src/berachain.rs index 99f88b75dd..7cccbffd3c 100644 --- a/lib/chain-utils/src/berachain.rs +++ b/lib/chain-utils/src/berachain.rs @@ -36,8 +36,8 @@ use unionlabs::{ use crate::{ ethereum::{ - self, balance_of_signers, EthereumChain, EthereumConsensusChain, EthereumExecutionRpcs, - EthereumSignerMiddleware, EthereumSignersConfig, ReadWrite, + self, balance_of_signers, EthereumChain, EthereumConsensusChain, EthereumSignerMiddleware, + EthereumSignersConfig, ReadWrite, }, keyring::{ChainKeyring, ConcurrentKeyring, SignerBalance}, }; @@ -189,7 +189,7 @@ impl Chain for Berachain { denominator: option_unwrap!(NonZeroU64::new(3)), } }, - trusting_period: const { result_unwrap!(Duration::new((UNBONDING_PERIOD * 85) / 100, 0)) }, + trusting_period: const { result_unwrap!(Duration::new(UNBONDING_PERIOD * 85 / 100, 0)) }, max_clock_drift: const { result_unwrap!(Duration::new(60 * 10, 0)) }, frozen_height: None, latest_height: Height { @@ -234,7 +234,7 @@ impl Chain for Berachain { } } -impl EthereumExecutionRpcs for Berachain { +impl EthereumChain for Berachain { fn provider(&self) -> Arc> { self.provider.clone() } diff --git a/lib/chain-utils/src/ethereum.rs b/lib/chain-utils/src/ethereum.rs index fe8c18cdab..002fabc294 100644 --- a/lib/chain-utils/src/ethereum.rs +++ b/lib/chain-utils/src/ethereum.rs @@ -52,7 +52,7 @@ pub type EthereumSignerMiddleware = // NOTE: ClientType bound is temporary until I figure out a better way to deal with client types /// A chain running the EVM and our solidity IBC stack. This can be any Ethereum L1 or L2, or a chain running the EVM in a different environment (such as Berachain). -pub trait EthereumExecutionRpcs { +pub trait EthereumChain: Chain { /// The provider connected to this chain's [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/). fn provider(&self) -> Arc>; @@ -60,9 +60,6 @@ pub trait EthereumExecutionRpcs { fn ibc_handler_address(&self) -> H160; } -pub trait EthereumChain = - EthereumExecutionRpcs + Chain; - /// An Ethereum-based chain. This can be any chain that is based off of and settles on Ethereum (i.e. Ethereum mainnet/ Sepolia, L2s such as Scroll). pub trait EthereumConsensusChain: EthereumChain { /// Fetch the execution height associated with the given beacon slot. For [`Ethereum`], this will simply be the execution block number, but for L2s this will fetch the settled height at the L1 block number. @@ -77,17 +74,17 @@ pub trait EthereumConsensusChain: EthereumChain { ) -> impl Future; } -#[diagnostic::on_unimplemented(message = "{Self} does not implement `EthereumExecutionRpcs`")] -pub trait EthereumExecutionRpcsExt: EthereumExecutionRpcs { +#[diagnostic::on_unimplemented(message = "{Self} does not implement `EthereumChain`")] +pub trait EthereumChainExt: EthereumChain { /// Convenience method to construct an [`IBCHandler`] instance for this chain. fn ibc_handler(&self) -> IBCHandler> { IBCHandler::new(self.ibc_handler_address(), self.provider()) } } -impl EthereumExecutionRpcsExt for T {} +impl EthereumChainExt for T {} -impl EthereumExecutionRpcs for Ethereum { +impl EthereumChain for Ethereum { fn provider(&self) -> Arc> { self.provider.clone() } diff --git a/lib/chain-utils/src/scroll.rs b/lib/chain-utils/src/scroll.rs index 888f113b8f..16a0ca32a4 100644 --- a/lib/chain-utils/src/scroll.rs +++ b/lib/chain-utils/src/scroll.rs @@ -18,8 +18,8 @@ use unionlabs::{ use crate::{ ethereum::{ self, balance_of_signers, Ethereum, EthereumChain, EthereumConsensusChain, - EthereumExecutionRpcs, EthereumInitError, EthereumKeyring, EthereumSignerMiddleware, - EthereumSignersConfig, ReadWrite, Readonly, + EthereumInitError, EthereumKeyring, EthereumSignerMiddleware, EthereumSignersConfig, + ReadWrite, Readonly, }, keyring::{ChainKeyring, ConcurrentKeyring, KeyringConfig, SignerBalance}, union::Union, @@ -95,7 +95,7 @@ impl ChainKeyring for Scroll { } } -impl EthereumExecutionRpcs for Scroll { +impl EthereumChain for Scroll { fn provider(&self) -> Arc> { self.provider.clone() } @@ -127,9 +127,7 @@ impl EthereumConsensusChain for Scroll { let proof = match <[_; 1]>::try_from(proof.storage_proof) { Ok([proof]) => proof, Err(invalid) => { - panic!( - "received invalid response from eth_getProof, expected length of 1 but got `{invalid:#?}`" - ); + panic!("received invalid response from eth_getProof, expected length of 1 but got `{invalid:#?}`"); } }; diff --git a/lib/relay-message/src/chain/arbitrum.rs b/lib/relay-message/src/chain/arbitrum.rs index a5f2f36ddf..b1ef7f9529 100644 --- a/lib/relay-message/src/chain/arbitrum.rs +++ b/lib/relay-message/src/chain/arbitrum.rs @@ -2,7 +2,7 @@ use std::{collections::VecDeque, marker::PhantomData}; use chain_utils::{ arbitrum::Arbitrum, - ethereum::{EthereumChain, EthereumConsensusChain, EthereumExecutionRpcsExt, IbcHandlerExt}, + ethereum::{EthereumChain, EthereumChainExt, EthereumConsensusChain, IbcHandlerExt}, }; use ethers::providers::Middleware; use frunk::{hlist_pat, HList}; diff --git a/lib/relay-message/src/chain/berachain.rs b/lib/relay-message/src/chain/berachain.rs index 2b31df16ed..e1d9b68b07 100644 --- a/lib/relay-message/src/chain/berachain.rs +++ b/lib/relay-message/src/chain/berachain.rs @@ -2,7 +2,7 @@ use std::{collections::VecDeque, marker::PhantomData}; use chain_utils::{ berachain::Berachain, - ethereum::{EthereumConsensusChain, EthereumExecutionRpcsExt, IbcHandlerExt}, + ethereum::{EthereumChainExt, EthereumConsensusChain, IbcHandlerExt}, }; use enumorph::Enumorph; use ethers::providers::Middleware; diff --git a/lib/relay-message/src/chain/ethereum.rs b/lib/relay-message/src/chain/ethereum.rs index f051443261..46819f3aba 100644 --- a/lib/relay-message/src/chain/ethereum.rs +++ b/lib/relay-message/src/chain/ethereum.rs @@ -1,9 +1,8 @@ use std::{collections::VecDeque, fmt::Debug, marker::PhantomData, ops::Div, sync::Arc}; use chain_utils::ethereum::{ - Ethereum, EthereumChain, EthereumConsensusChain, EthereumExecutionRpcsExt as _, - EthereumKeyring, EthereumSignerMiddleware, IbcHandlerErrors, IbcHandlerExt, - ETHEREUM_REVISION_NUMBER, + Ethereum, EthereumChain, EthereumChainExt as _, EthereumConsensusChain, EthereumKeyring, + EthereumSignerMiddleware, IbcHandlerErrors, IbcHandlerExt, ETHEREUM_REVISION_NUMBER, }; use contracts::ibc_handler::{ self, AcknowledgePacketCall, ChannelOpenAckCall, ChannelOpenConfirmCall, ChannelOpenInitCall, @@ -102,6 +101,7 @@ where StoredClientState>: Encode, StateProof: Encode, >, + ClientStateOf>: Encode, AnyLightClientIdentified: From)>, { @@ -443,6 +443,7 @@ impl DoFetchState for Ethereum where C: ChainSpec, Tr: ChainExt>> + Encode>, + AnyLightClientIdentified: From, Tr>)>, { type QueryUnfinalizedTrustedClientStateError = Never; @@ -1043,15 +1044,18 @@ impl DoAggregate for Identified, Tr, EthereumAggregateMsg>, + Identified, Tr, AccountUpdateData>: IsAggregateData, Identified, Tr, BootstrapData>: IsAggregateData, Identified, Tr, BeaconGenesisData>: IsAggregateData, Identified, Tr, FinalityUpdate>: IsAggregateData, Identified, Tr, LightClientUpdates>: IsAggregateData, Identified, Tr, LightClientUpdate>: IsAggregateData, + AnyLightClientIdentified: From, Tr>)>, AnyLightClientIdentified: From>)>, AnyLightClientIdentified: From>)>, + AnyLightClientIdentified: From, Tr>)>, AnyLightClientIdentified: From, Tr>)>, { @@ -1181,9 +1185,11 @@ impl UseAggregate for Identified, Tr, CreateUpd where C: ChainSpec, Tr: ChainExt, + Identified, Tr, AccountUpdateData>: IsAggregateData, Identified, Tr, LightClientUpdate>: IsAggregateData, Identified, Tr, BeaconGenesisData>: IsAggregateData, + AnyLightClientIdentified: From>)>, AnyLightClientIdentified: From>)>, { @@ -1262,7 +1268,7 @@ where req.counterparty_chain_id.clone(), WaitForTimestamp { timestamp: (genesis.genesis_time - + header.consensus_update.signature_slot * C::SECONDS_PER_SLOT::U64) + + (header.consensus_update.signature_slot * C::SECONDS_PER_SLOT::U64)) .try_into() .unwrap(), __marker: PhantomData, @@ -1283,7 +1289,9 @@ impl UseAggregate for Identified, Tr, MakeCreat where C: ChainSpec, Tr: ChainExt, + Identified, Tr, FinalityUpdate>: IsAggregateData, + AnyLightClientIdentified: From, Tr>)>, AnyLightClientIdentified: From, Tr>)>, { @@ -1299,10 +1307,10 @@ where chain_id: bootstrap_chain_id, t: FinalityUpdate { finality_update, - __marker: _, + __marker: _ }, __marker: _, - }]: Self::AggregatedData, + },]: Self::AggregatedData, ) -> Op { assert_eq!(chain_id, bootstrap_chain_id); @@ -1312,9 +1320,9 @@ where let trusted_period = sync_committee_period::<_, C>(req.update_from.revision_height); assert!( - trusted_period <= target_period, - "trusted period {trusted_period} is behind target period {target_period}, something is wrong!" - ); + trusted_period <= target_period, + "trusted period {trusted_period} is behind target period {target_period}, something is wrong!", + ); // Eth chain is more than 1 signature period ahead of us. We need to do sync committee // updates until we reach the `target_period - 1`. @@ -1344,13 +1352,16 @@ impl UseAggregate where C: ChainSpec, Tr: ChainExt>, + Identified, Tr, LightClientUpdates>: IsAggregateData, + AnyLightClientIdentified: From>)>, AnyLightClientIdentified: From>)>, AnyLightClientIdentified: From, Tr>)>, AnyLightClientIdentified: From, Tr>)>, - AnyLightClientIdentified: From, Tr>)>, // Identified, Tr, LightClientUpdates>: - // TryFrom>, + AnyLightClientIdentified: From, Tr>)>, + // Identified, Tr, LightClientUpdates>: + // TryFrom>, { type AggregatedData = HList![Identified, Tr, LightClientUpdates>]; @@ -1372,7 +1383,7 @@ where __marker: _, }, __marker: _, - }]: Self::AggregatedData, + },]: Self::AggregatedData, ) -> Op { assert_eq!(chain_id, light_client_updates_chain_id); diff --git a/lib/relay-message/src/chain/scroll.rs b/lib/relay-message/src/chain/scroll.rs index 8e4dc5b1ab..8f5b87b8f4 100644 --- a/lib/relay-message/src/chain/scroll.rs +++ b/lib/relay-message/src/chain/scroll.rs @@ -4,7 +4,7 @@ use std::{ }; use chain_utils::{ - ethereum::{EthereumChain, EthereumConsensusChain, EthereumExecutionRpcsExt, IbcHandlerExt}, + ethereum::{EthereumChain, EthereumChainExt, EthereumConsensusChain, IbcHandlerExt}, scroll::Scroll, }; use ethers::{abi::AbiDecode, providers::Middleware}; diff --git a/sentinel-config-testnet.json b/sentinel-config-testnet.json deleted file mode 100644 index b8a61594ce..0000000000 --- a/sentinel-config-testnet.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "chain_configs": { - "ethereum": { - "ethereum": { - "enabled": true, - "ibc_handler_address": "0xa390514f803a3b318b93bf6cd4beeb9f8299a0eb", - "signers": [ - { "raw": "0xc7a7febac36c64d3cd757747494e4de734af5f05339a2fc818f589709e738fff" } - ], - "eth_rpc_api": "wss://eth-sepolia.g.alchemy.com/v2/Xn_VBUDyUtXUYb9O6b5ZmuBNDaSlH-BB", - "transfer_module": { - "type": "contract", - "address": "0xd0081080ae8493cf7340458eaf4412030df5feeb" - } - } - }, - "bera": { - "ethereum": { - "enabled": true, - "ibc_handler_address": "0x4e86d3eb0f4d8ddccec2b8fa5ccfc8170e8ac3dc", - "signers": [ - { - "raw": "0xc56de6bf91c78afb4757055a080e6c3a53f83e588750e39196f4e65931bc86a2" - } - ], - "eth_rpc_api": "wss://fabled-serene-mountain.bera-bartio.quiknode.pro/6ab3f499dcce3d52591ce97a5f07a13fae75deb1", - "transfer_module": { - "type": "contract", - "address": "0x0e7aee8a4109b1c1916281d25f43b937f103a409" - } - } - }, - "osmosis": { - "cosmos": { - "enabled": false, - "chain_config": { - "keyring": { - "name": "osmosis-testnet", - "keys": [ - { - "type": "raw", - "name": "osmosis-testnet-key0", - "key": "0x1503e463998e28b130a2d4876632c80462bbd5e0d9eb7ce6ed5f6210f02a2913" - } - ] - }, - "gas_config": { - "gas_price": "1.0", - "gas_denom": "uosmo", - "gas_multiplier": "1.1", - "max_gas": 400000 - }, - "fee_denom": "uosmo", - "ws_url": "wss://rpc.osmo.test.yieldpay.finance/websocket", - "grpc_url": "https://grpc.osmo.test.yieldpay.finance:443" - }, - "transfer_module": { - "type": "native" - } - } - }, - "union": { - "cosmos": { - "enabled": true, - "chain_config": { - "keyring": { - "name": "union-testnet", - "keys": [ - { - "type": "raw", - "name": "union-testnet-key0", - "key": "0xe6b7f3906f38ea3547c91ed2f5eab850d27dd5672424fa4759e471be45598860" - } - ] - }, - - "gas_config": { - "gas_price": "1.0", - "gas_denom": "muno", - "gas_multiplier": "1.1", - "max_gas": 400000 - }, - "fee_denom": "muno", - "ws_url": "wss://rpc.testnet.bonlulu.uno/websocket", - "prover_endpoint": "https://galois.testnet-8.union.build:443", - "grpc_url": "https://grpc.testnet.bonlulu.uno" - }, - "transfer_module": { - "type": "contract", - "address": "union177jpkxrhvzca0dhr7p05ty595ucdgdl6k4wv67500jxcu6t5hppqemdy20" - } - } - } - }, - "interactions": [ - { - "source": { - "chain": "union", - "channel": "channel-6" - }, - "destination": { - "chain": "bera", - "channel": "channel-80" - }, - "protocol": { - "Ucs01": { - "receivers": ["0x614E946f6D769Ad2983E4d4B81DDeBBFA51B09b5"], - "contract": "union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h" - } - }, - "memo": "{\"forward\":{\"receiver\":\"614E946f6D769Ad2983E4d4B81DDeBBFA51B09b5\",\"port\":\"wasm.union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h\",\"channel\":\"channel-80\"}}", - "sending_memo_probability": 0, - "denoms": ["muno"], - "send_packet_interval": 1, - "expect_full_cycle": 900, - "amount_min": 1, - "amount_max": 1, - "max_retry": 3 - } - ], - "single_interaction": { - "source": { - "chain": "union", - "channel": "channel-6" - }, - "destination": { - "chain": "bera", - "channel": "channel-80" - }, - "protocol": { - "Ucs01": { - "receivers": ["0x614E946f6D769Ad2983E4d4B81DDeBBFA51B09b5"], - "contract": "union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h" - } - }, - "memo": "{\"forward\":{\"receiver\":\"614E946f6D769Ad2983E4d4B81DDeBBFA51B09b5\",\"port\":\"wasm.union1m87a5scxnnk83wfwapxlufzm58qe2v65985exff70z95a2yr86yq7hl08h\",\"channel\":\"channel-80\"}}", - "sending_memo_probability": 0, - "denoms": ["0xa1d2D7AC1185DBC9554F51489BD648BBcA95e928"], - "send_packet_interval": 1, - "expect_full_cycle": 900, - "amount_min": 1, - "amount_max": 1, - "max_retry": 3 - } -} diff --git a/sentinel-config.json b/sentinel-config.json deleted file mode 100644 index 484a388a84..0000000000 --- a/sentinel-config.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "ethereum": { - "chain_config": { - "ibc_handler_address": "0xed2af2ad7fe0d92011b26a2e5d1b4dc7d12a47c5", - "signers": [ - { - "raw": "0x4e9444a6efd6d42725a250b650a781da2737ea308c839eaccb0f7f3dbd2fea77" - } - ], - "eth_rpc_api": "ws://localhost:8546", - "eth_beacon_rpc_api": "http://localhost:9596" - }, - "address": "0xed2af2ad7fe0d92011b26a2e5d1b4dc7d12a47c5", - "channel": "channel-0", - "counterparty_channel": "channel-0" - }, - "osmosis": { - "chain_config": { - "chain_config": { - "signers": [ - { - "raw": "0xaa820fa947beb242032a41b6dc9a8b9c37d8f5fbcda0966b1ec80335b10a7d6f" - } - ], - "fee_denom": "uosmo", - "ws_url": "ws://localhost:26857/websocket", - "grpc_url": "http://localhost:9290" - }, - "address": "osmo1jk9psyhvgkrt2cumz8eytll2244m2nnzhgejsf", - "channel": "channel-0", - "counterparty_channel": "channel-0" - }, - "protocol": "Ics20" - }, - "union": { - "chain_config": { - "chain_config": { - "signers": [ - { - "raw": "0xaa820fa947beb242032a41b6dc9a8b9c37d8f5fbcda0966b1ec80335b10a7d6f" - } - ], - "fee_denom": "muno", - "ws_url": "ws://localhost:26657/websocket", - "prover_endpoint": "https://galois.testnet-8.union.build:443", - "grpc_url": "http://localhost:9090" - }, - "address": "union1jk9psyhvgkrt2cumz8eytll2244m2nnz4yt2g2", - "channel": "channel-0", - "counterparty_channel": "channel-0" - }, - "protocol": { - "Ucs01": { - "contract": "union177jpkxrhvzca0dhr7p05ty595ucdgdl6k4wv67500jxcu6t5hppqemdy20" - } - } - }, - "amount": "1", - "db_url": "postgres://postgres:postgrespassword@127.0.0.1:5432/default", - "connections": [ - { - "source_chain": "union", - "target_chain": "osmosis", - "send_packet_interval": 2500, - "expect_full_circle": 10 - } - ] -} diff --git a/sentinel/Cargo.toml b/sentinel/Cargo.toml deleted file mode 100644 index c3fc674e97..0000000000 --- a/sentinel/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -edition = { workspace = true } -license-file = { workspace = true } -name = "sentinel" -publish = false -repository = { workspace = true } -version = "0.1.0" - -[lints] -workspace = true - -[dependencies] -bech32 = "0.9.1" -chain-utils = { workspace = true } -chrono = { workspace = true, features = ["serde"] } -clap = { workspace = true, features = ["default", "derive", "env", "error-context"] } -contracts = { workspace = true } -cosmwasm-std = { version = "2.0.0", features = ["stargate", "cosmwasm_1_3"] } -csv = "1.3.0" -dashmap = { workspace = true } -derive_more = { workspace = true } -ethers = { workspace = true, features = ["rustls", "ws"] } -futures = { workspace = true } -hex = { version = "0.4.3", default-features = false } -parking_lot = "0.11" -prost = { workspace = true } -protos = { workspace = true, features = ["default", "client"] } -queue-msg = { workspace = true } -rand = "0.8" -reqwest = { workspace = true, features = ["json"] } -serde = { workspace = true, features = ["derive"] } -serde-utils = { workspace = true } -serde_json = { workspace = true } -sqlx = { workspace = true, features = ["postgres", "runtime-tokio", "tls-rustls", "time", "macros", "json", "chrono"] } -tendermint = { workspace = true } -tendermint-proto = { workspace = true } -tendermint-rpc = { workspace = true, features = ["http-client", "websocket-client"] } -tokio = { workspace = true, features = ["macros", "signal"] } -tokio-stream = { workspace = true } -tonic = { workspace = true, features = ["transport", "tls", "tls-roots", "tls-webpki-roots"] } -tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["env-filter", "json"] } -ucs01-relay = { workspace = true, features = ["library"] } -ucs01-relay-api = { workspace = true } -unionlabs = { workspace = true, features = ["ethabi"] } -uuid = { version = "1.5.0", features = ["serde", "v4"] } diff --git a/sentinel/sentinel.md b/sentinel/sentinel.md deleted file mode 100644 index 9f24587ecb..0000000000 --- a/sentinel/sentinel.md +++ /dev/null @@ -1,179 +0,0 @@ - - -# Sentinel - -## Use cases - -1. Send native tokens and wrapped tokens back periodically. - -2. Trace the ibc events. - -## Structure - -### Config file - -1. Chain's contain `chain_config` for `chain_utils::::Config`. - -```json -{ - "chain_configs": { - "ethereum": { - "ethereum": { - "enabled": true, - "ibc_handler_address": "0xa390514f803a3b318b93bf6cd4beeb9f8299a0eb", - "signers": [ - { - "raw": "0xc7a7febac36c64d3cd757747494e4de734af5f05339a2fc818f589709e738fff" - } - ], - "eth_rpc_api": "wss://eth-sepolia.g.alchemy.com/v2/Xn_VBUDyUtXUYb9O6b5ZmuBNDaSlH-BB", - "transfer_module": { - "type": "contract", - "address": "0xd0081080ae8493cf7340458eaf4412030df5feeb" - } - } - }, - "osmosis": { - "cosmos": { - "enabled": false, - "chain_config": { - "keyring": { - "name": "osmosis-testnet", - "keys": [ - { - "type": "raw", - "name": "osmosis-testnet-key0", - "key": "0x1503e463998e28b130a2d4876632c80462bbd5e0d9eb7ce6ed5f6210f02a2913" - } - ] - }, - "gas_config": { - "gas_price": "1.0", - "gas_denom": "uosmo", - "gas_multiplier": "1.1", - "max_gas": 400000 - }, - "fee_denom": "uosmo", - "ws_url": "wss://rpc.osmo.test.yieldpay.finance/websocket", - "grpc_url": "https://grpc.osmo.test.yieldpay.finance:443" - }, - "transfer_module": { - "type": "native" - } - } - }, - "union": { - "cosmos": { - "enabled": true, - "chain_config": { - "keyring": { - "name": "union-testnet", - "keys": [ - { - "type": "raw", - "name": "union-testnet-key0", - "key": "0xe6b7f3906f38ea3547c91ed2f5eab850d27dd5672424fa4759e471be45598860" - } - ] - }, - "gas_config": { - "gas_price": "1.0", - "gas_denom": "muno", - "gas_multiplier": "1.1", - "max_gas": 400000 - }, - "fee_denom": "muno", - "ws_url": "wss://rpc.testnet.bonlulu.uno/websocket", - "prover_endpoint": "https://galois.testnet-8.union.build:443", - "grpc_url": "https://grpc.testnet.bonlulu.uno" - }, - "transfer_module": { - "type": "contract", - "address": "union177jpkxrhvzca0dhr7p05ty595ucdgdl6k4wv67500jxcu6t5hppqemdy20" - } - } - } - }, - "interactions": [ - { - "source": { - "chain": "ethereum", - "channel": "channel-78" - }, - "destination": { - "chain": "union", - "channel": "channel-83" - }, - "protocol": { - "Ucs01": { - "receivers": ["union1qgvmcfkpd66wat6shhfas0z8z9dzp683mcj9tq"], - "contract": "union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa" - } - }, - "memo": "{\"forward\":{\"receiver\":\"84cB5E16918547aD6181fe6513861a7eA476f2EC\",\"port\":\"wasm.union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa\",\"channel\":\"channel-70\"}}", - "sending_memo_probability": 1, - "denoms": [ - "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238", - "muno", - "0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4" - ], - "send_packet_interval": 50, - "expect_full_cycle": 35, - "amount_min": 1, - "amount_max": 3 - }, - { - "source": { - "chain": "union", - "channel": "channel-83" - }, - "destination": { - "chain": "ethereum", - "channel": "channel-78" - }, - "protocol": { - "Ucs01": { - "receivers": ["0xfbf2b6f136feb11b738592c7c5cf63b83825ff46"], - "contract": "union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa" - } - }, - "memo": "{\"forward\":{\"receiver\":\"84cB5E16918547aD6181fe6513861a7eA476f2EC\",\"port\":\"wasm.union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa\",\"channel\":\"channel-78\"}}", - "sending_memo_probability": 0.0, - "denoms": [ - "factory/union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa/0xe619529b4396a62ab6d88ff2bb195e83c11e909ad9", - "factory/union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa/0x742f7232c73bea91be2828fa14129f68015c3f895b", - "muno" - ], - "send_packet_interval": 50, - "expect_full_cycle": 35, - "amount_min": 1, - "amount_max": 3 - } - ], - "single_interaction": { - "source": { - "chain": "ethereum", - "channel": "channel-78" - }, - "destination": { - "chain": "union", - "channel": "channel-83" - }, - "protocol": { - "Ucs01": { - "receivers": ["union1qgvmcfkpd66wat6shhfas0z8z9dzp683mcj9tq"], - "contract": "union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa" - } - }, - "memo": "{\"forward\":{\"receiver\":\"614E946f6D769Ad2983E4d4B81DDeBBFA51B09b5\",\"port\":\"wasm.union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa\",\"channel\":\"channel-79\"}}", - "sending_memo_probability": 0, - "denoms": [ - "factory/union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa/0x742f7232c73bea91be2828fa14129f68015c3f895b" - ], - "send_packet_interval": 1, - "expect_full_cycle": 1, - "amount_min": 1, - "amount_max": 1 - } -} -``` diff --git a/sentinel/sentinel.nix b/sentinel/sentinel.nix deleted file mode 100644 index 5f6ba41706..0000000000 --- a/sentinel/sentinel.nix +++ /dev/null @@ -1,72 +0,0 @@ -{ self, ... }: { - perSystem = { self', pkgs, system, config, crane, stdenv, dbg, ... }: - let - sentinel = crane.buildWorkspaceMember { - crateDirFromRoot = "sentinel"; - extraEnv = { - SQLX_OFFLINE = "1"; - }; - }; - in - { - packages = sentinel.packages; - - }; - flake.nixosModules.sentinel = { lib, pkgs, config, ... }: - with lib; - let - cfg = config.services.sentinel; - in - { - options.services.sentinel = { - enable = mkEnableOption "Sentinel service"; - package = mkOption { - type = types.package; - default = self.packages.${pkgs.system}.sentinel; - }; - chain_configs = mkOption { - type = types.attrs; - }; - interactions = mkOption { - type = types.listOf types.attrs; - }; - log-level = mkOption { - type = types.str; - default = "info"; - description = "RUST_LOG passed to sentinel"; - example = "sentinel=info"; - }; - log-format = mkOption { - type = types.enum [ "json" "text" ]; - default = "json"; - example = "text"; - }; - }; - config = - let - configJson = pkgs.writeText "config.json" (builtins.toJSON { - chain_configs = cfg.chain_configs; - interactions = cfg.interactions; - }); - - in - mkIf cfg.enable { - systemd.services.sentinel = { - description = "Sentinel"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - serviceConfig = { - Type = "simple"; - ExecStart = "${pkgs.lib.getExe cfg.package} --config ${configJson} -l ${cfg.log-format}"; - Restart = "always"; - RestartSec = 10; - # User = "sentinel"; - # Group = "sentinel"; - }; - environment = { - RUST_LOG = "INFO"; - }; - }; - }; - }; -} diff --git a/sentinel/src/chains.rs b/sentinel/src/chains.rs deleted file mode 100644 index 658059fdcf..0000000000 --- a/sentinel/src/chains.rs +++ /dev/null @@ -1,1257 +0,0 @@ -use std::{collections::HashMap, str::FromStr, sync::Arc, time::Duration}; - -use bech32::FromBase32; -use chain_utils::{ - cosmos_sdk::{BroadcastTxCommitError, CosmosSdkChainExt}, - ethereum::{EthereumExecutionRpcs, EthereumExecutionRpcsExt, IBCHandlerEvents}, -}; -use chrono::Utc; -use contracts::{ - erc20, - ibc_packet::IBCPacketEvents, - ucs01_relay::{LocalToken, UCS01Relay}, -}; -use ethers::{ - abi::RawLog, - contract::EthLogDecode, - core::k256::ecdsa, - middleware::{NonceManagerMiddleware, SignerMiddleware}, - providers::{Middleware, Provider, Ws}, - signers::LocalWallet, - types::{Address, Filter, H256}, - utils::secret_key_to_address, -}; -use futures::StreamExt; -use hex::{self, encode as hex_encode}; -use prost::Message; -use protos::{google::protobuf::Any, ibc::applications::transfer::v1::MsgTransfer}; -use rand::{prelude::SliceRandom, rngs::StdRng, Rng, SeedableRng}; -use serde::{Deserialize, Serialize}; -use tendermint_rpc::{event::EventData, SubscriptionClient}; -use ucs01_relay::msg::{ExecuteMsg, TransferMsg}; -use ucs01_relay_api::types::{Ics20Ack, JsonWasm, Ucs01Ack}; -use unionlabs::{ - cosmos::base::coin::Coin, - cosmwasm::wasm::msg_execute_contract::MsgExecuteContract, - encoding::{self, DecodeAs}, - events::{AcknowledgePacket, RecvPacket, SendPacket, WriteAcknowledgement}, - google::protobuf::any, - hash::H160, - ibc::core::{ - channel::channel::{self, Channel}, - client::height::Height, - }, - id::{ChannelId, ClientId}, - tendermint::abci::{event::Event as TendermintEvent, event_attribute::EventAttribute}, - uint::U256, - validated::ValidateT, -}; - -use crate::{ - config::{CosmosConfig, EthereumConfig, EventTrackerConfig, TransferModule}, - context::EventStateMap, -}; -pub type IbcEvent = unionlabs::events::IbcEvent; - -pub trait IbcTransfer: Send + Sync { - async fn send_ibc_transfer( - &self, - protocol: Protocol, - channel: ChannelId, - destination_channel: ChannelId, - denom: String, - amount: u64, - memo: String, - max_retry: u64, - ); -} - -pub trait IbcListen: Send + Sync { - async fn listen(&self, event_state_map: &EventStateMap); - - // TODO(caglankaan): How can i know the protocol type here? On listen we don't know what is the destination chain - // It can be anything, if i am listening on union since there is only one listener for union there could be 2 different - // chains which are sending request to me 1- ethereum with ucs01 and 2- osmosis with ics20 so i am not sure how can i know - // the protocol here. For know i'll try bruteforce but it's not a good solution. - fn write_handler_packet_ack_hex_controller( - &self, - ack_hex: Vec, //protocol: Protocol - ) -> bool { - // match protocol { - // Protocol::Ics20 => { - // let val = Ics20Ack::try_from(cosmwasm_std::Binary::from(ack_hex)).unwrap(); - // match val { - // Ics20Ack::Result(_) => { - // return true; - // } - // Ics20Ack::Error(_) => { - // return false; - // } - // } - // } - // Protocol::Ucs01 => { - // return ( - // Ucs01Ack::try_from(cosmwasm_std::Binary::from(ack_hex)).unwrap() == - // Ucs01Ack::Success - // ); - // } - // _ => { - // tracing::error!("Unknown protocol {:?} -> {:?}", protocol, ack_hex); - // return false; - // } - // } - - // Try to decode as Ics20Ack first; - if let Ok(val) = - Ics20Ack::decode_as::(cosmwasm_std::Binary::from(ack_hex.clone()).as_slice()) - { - match val { - Ics20Ack::Result(_) => { - return true; - } - Ics20Ack::Error(_) => { - tracing::warn!("Ics20Ack::Result failed decode."); - } - } - } - - if let Ok(val) = Ucs01Ack::decode_as::( - cosmwasm_std::Binary::from(ack_hex.clone()).as_slice(), - ) { - return val == Ucs01Ack::Success; - } else { - tracing::warn!("Failed to decode ack_hex: {:?} ", ack_hex); - return false; - } - } - - async fn handle_ibc_event( - &self, - ibc_event: IbcEvent, - event_state_map: &EventStateMap, - block_number: u64, - tx_hash: Option, - ); - - fn handle_ibc_event_boxed<'a>( - &'a self, - ibc_event: IbcEvent, - event_state_map: &'a EventStateMap, - _block_number: u64, - tx_hash: Option, - ) -> std::pin::Pin + Send + 'a>> { - Box::pin(async move { - let (packet_sequence, key) = match &ibc_event { - IbcEvent::SendPacket(e) => ( - e.packet_sequence, - format!("{}->{}", e.packet_src_channel, e.packet_dst_channel), - ), - IbcEvent::RecvPacket(e) => ( - e.packet_sequence, - format!("{}->{}", e.packet_src_channel, e.packet_dst_channel), - ), - IbcEvent::WriteAcknowledgement(e) => ( - e.packet_sequence, - format!("{}->{}", e.packet_src_channel, e.packet_dst_channel), - ), - IbcEvent::AcknowledgePacket(e) => ( - e.packet_sequence, - format!("{}->{}", e.packet_src_channel, e.packet_dst_channel), - ), - // Handle other events if necessary, - _ => { - return; - } - }; - - let sequence: i32 = packet_sequence.get() as i32; - - let mut entry = event_state_map - .entry(key.clone()) - .or_insert_with(HashMap::new); - - let sequence_entry = entry.entry(sequence).or_insert_with(|| { - let mut event_map = HashMap::new(); - for idx in 0..4 { - event_map.insert( - idx, - EventTrackerConfig { - idx, - arrived: false, - arrived_time: None, - tx_hash: None, - }, - ); - } - event_map - }); - - match ibc_event { - IbcEvent::SendPacket(event) => { - if let Some(event_data) = sequence_entry.get_mut(&0) { - event_data.arrived = true; - event_data.arrived_time = Some(chrono::Utc::now()); - event_data.tx_hash = tx_hash; - tracing::info!( - sequence = sequence, - key = key, - "SendPacket event recorded. Tx Hash: {:?}", - event_data.tx_hash - ); - } else { - tracing::warn!( - "Unexpected error: Could not find event data for SendPacket." - ); - } - } - IbcEvent::RecvPacket(_) => { - if sequence_entry.get(&0).map_or(false, |e| e.arrived) { - if let Some(event_data) = sequence_entry.get_mut(&1) { - event_data.arrived = true; - event_data.arrived_time = Some(chrono::Utc::now()); - event_data.tx_hash = tx_hash; - tracing::info!( - sequence = sequence, - key = key, - "RecvPacket event recorded. Tx Hash: {:?}", - event_data.tx_hash - ); - } else { - tracing::warn!( - "Unexpected error: Could not find event data for RecvPacket." - ); - } - } else { - tracing::warn!( - sequence = sequence, - key = key, - "RecvPacket event received without SendPacket. Tx Hash: {:?}", - tx_hash - ); - entry.remove(&sequence); - } - } - IbcEvent::WriteAcknowledgement(ref e) => { - if sequence_entry.get(&0).map_or(false, |e| e.arrived) { - if self.write_handler_packet_ack_hex_controller(e.packet_ack_hex.clone()) { - if let Some(event_data) = sequence_entry.get_mut(&2) { - event_data.arrived = true; - event_data.arrived_time = Some(chrono::Utc::now()); - event_data.tx_hash = tx_hash; - tracing::info!( - sequence = sequence, - key = key, - "WriteAcknowledgement event recorded. Tx Hash: {:?}", - event_data.tx_hash - ); - } else { - tracing::warn!( - "Unexpected error: Could not find event data for WriteAcknowledgement." - ); - } - } else { - tracing::error!( - sequence = sequence, - key = key, - "[TRANSFER FAILED] WriteAcknowledgement indicates failure. packet_ack_hex: {:?}. Tx Hash: {:?}", - e.packet_ack_hex.clone(), - tx_hash - ); - entry.remove(&sequence); - } - } else { - tracing::warn!( - sequence = sequence, - key = key, - "WriteAcknowledgement event received without SendPacket. Tx Hash: {:?}", - tx_hash - ); - entry.remove(&sequence); - } - } - IbcEvent::AcknowledgePacket(_) => { - if sequence_entry.get(&0).map_or(false, |e| e.arrived) - && sequence_entry.get(&1).map_or(false, |e| e.arrived) - && sequence_entry.get(&2).map_or(false, |e| e.arrived) - { - if let Some(event_data) = sequence_entry.get_mut(&3) { - event_data.arrived = true; - event_data.arrived_time = Some(chrono::Utc::now()); - event_data.tx_hash = tx_hash; - tracing::info!( - sequence = sequence, - key = key, - "AcknowledgePacket event recorded. Tx Hash: {:?}", - event_data.tx_hash - ); - - if sequence_entry.values().all(|event_data| event_data.arrived) { - tracing::info!( - sequence = sequence, - "All events completed. sequence_entry: {:?} Tx Hash: {:?}", - sequence_entry, - tx_hash - ); - entry.remove(&sequence); - } - } else { - tracing::warn!( - "Unexpected error: Could not find event data for AcknowledgePacket." - ); - } - } else { - tracing::warn!( - sequence = sequence, - key = key, - "AcknowledgePacket event received out of order for sequence: {}. key: {} Tx Hash: {:?}", - sequence, - key, - tx_hash - ); - entry.remove(&sequence); - } - } - _ => { - return; - } - } - }) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum Protocol { - Ics20 { - receivers: Vec, // Changed to Vec - module: TransferModule, - }, - Ucs01 { - receivers: Vec, //Vec>, - contract: String, - }, -} - -#[derive(Debug, Clone)] -pub enum Chain { - Ethereum(Ethereum), - Cosmos(Cosmos), -} - -impl IbcListen for Chain { - async fn listen(&self, event_state_map: &EventStateMap) { - match self { - Chain::Ethereum(ethereum) => { - ethereum.listen(event_state_map).await; - } - Chain::Cosmos(cosmos) => { - cosmos.listen(event_state_map).await; - } - } - } - - async fn handle_ibc_event( - &self, - ibc_event: IbcEvent, - event_state_map: &EventStateMap, - block_number: u64, - tx_hash: Option, - ) { - match self { - Chain::Ethereum(ethereum) => { - ethereum - .handle_ibc_event(ibc_event, event_state_map, block_number, tx_hash) - .await; - } - Chain::Cosmos(cosmos) => { - cosmos - .handle_ibc_event(ibc_event, event_state_map, block_number, tx_hash) - .await; - } - } - } - - fn write_handler_packet_ack_hex_controller( - &self, - ack_hex: Vec, // protocol: Protocol // TODO: Add it after find a way - ) -> bool { - IbcListen::write_handler_packet_ack_hex_controller(self, ack_hex /* , protocol*/) - } -} - -impl IbcTransfer for Chain { - async fn send_ibc_transfer( - &self, - protocol: Protocol, - channel: ChannelId, - destination_channel: ChannelId, - denom: String, - amount: u64, - memo: String, - max_retry: u64, - ) { - match self { - Chain::Ethereum(ethereum) => { - ethereum - .send_ibc_transfer( - protocol, - channel, - destination_channel, - denom, - amount, - memo, - max_retry, - ) - .await; - } - Chain::Cosmos(cosmos) => { - cosmos - .send_ibc_transfer( - protocol, - channel, - destination_channel, - denom, - amount, - memo, - max_retry, - ) - .await; - } - } - } -} - -#[derive(Debug, Clone)] -pub struct Ethereum { - pub rpc: EthereumRpc, - pub relays: - Vec>>, LocalWallet>>>, - pub signer_middlewares: Arc< - Vec< - tokio::sync::Mutex< - Arc>>, LocalWallet>>, - >, - >, - >, - pub ucs01_contract: String, - pub msg_senders: Vec, - pub relay_addr: ethers::types::H160, -} - -#[derive(Debug, Clone)] -pub struct EthereumRpc { - pub provider: Arc>, - pub ibc_handler_address: H160, -} - -impl EthereumExecutionRpcs for EthereumRpc { - fn provider(&self) -> Arc> { - self.provider.clone() - } - - fn ibc_handler_address(&self) -> H160 { - self.ibc_handler_address - } -} - -impl IbcListen for Ethereum { - async fn listen(&self, event_state_map: &EventStateMap) { - let mut latest_checked_block = 0; - loop { - let provider = self.rpc.provider.clone(); - - let latest_block: u64 = provider.get_block_number().await.unwrap().as_u64(); - if latest_checked_block >= latest_block { - tokio::time::sleep(Duration::from_secs(1)).await; - continue; - } - latest_checked_block = latest_block; - let chain_id = provider - .get_chainid() - .await - .expect("Failed to get chain ID") - .as_u64(); - tracing::info!( - block = latest_block, - chain_id = chain_id, - "Fetching Ethereum latest_block." - ); - // Update the filter to fetch logs from the latest block processed + 1 - let filter = Filter::new() - .address(ethers::types::H160::from(self.rpc.ibc_handler_address)) - .from_block(latest_block) - .to_block(latest_block); - - let logs = provider.get_logs(&filter).await.unwrap(); - - let logs_clone = logs.clone(); // Clone logs for processing - futures::stream::iter(logs_clone.clone()) - .filter_map(|log| async move { - let raw_log = RawLog { - topics: log.topics.clone(), - data: log.data.clone().to_vec(), - }; - let transaction_hash = log.transaction_hash; - Some((raw_log, transaction_hash)) - }) - .for_each_concurrent(None, |(raw_log, transaction_hash)| { - let value = logs_clone.clone(); - async move { - let ibc_event: Option< - unionlabs::events::IbcEvent< - unionlabs::validated::Validated< - String, - ( - unionlabs::id::Bounded<9, 64>, - unionlabs::id::Ics024IdentifierCharacters, - ), - >, - String, - unionlabs::validated::Validated< - String, - ( - unionlabs::id::Bounded<9, 64>, - unionlabs::id::Ics024IdentifierCharacters, - ), - >, - >, - > = ibchandler_events_to_ibc_event(raw_log, &self.rpc, latest_block).await; - - if let Some(ibc_event) = ibc_event { - self.handle_ibc_event( - ibc_event, - &event_state_map, - latest_block, - transaction_hash, - ) - .await; - } - } - }) - .await; - } - } - - async fn handle_ibc_event( - &self, - ibc_event: IbcEvent, - event_state_map: &EventStateMap, - block_number: u64, - tx_hash: Option, - ) { - IbcListen::handle_ibc_event_boxed(self, ibc_event, event_state_map, block_number, tx_hash) - .await; - } -} -impl IbcListen for Cosmos { - async fn listen(&self, event_state_map: &EventStateMap) { - tracing::info!("Listening to Cosmos chain events"); - let mut subs = self - .chain - .tm_client - .subscribe(tendermint_rpc::query::EventType::Tx.into()) - .await - .unwrap(); - loop { - tokio::select! { - Some(event_result) = subs.next() => { - match event_result { - Ok(event) => { - if let Some(ref events) = event.events { - if let Some(heights) = events.get("tx.height") { - if let Some(height) = heights.first() { - let block_number: u64 = height.parse().expect("Failed to parse block number"); - tracing::info!("Fetched cosmos Block number: {}", block_number); - - if let Some(tx_hashes) = events.get("tx.hash") { - if let Some(tx_hash) = tx_hashes.first() { - let tx_hash = H256::from_str(tx_hash).expect("Failed to parse transaction hash"); - - match event.data { - EventData::Tx { tx_result, .. } => { - for event in tx_result.result.events { - let some_event = IbcEvent::try_from_tendermint_event(TendermintEvent { - ty: event.kind, - attributes: event.attributes - .into_iter() - .map(|attr| EventAttribute { - key: attr.key, - value: attr.value, - index: attr.index, - }) - .collect(), - }); - - if let Some(Ok(ibc_event)) = some_event { - self.handle_ibc_event(ibc_event, &event_state_map, block_number, tx_hash.into()).await; - } - } - } - _ => { - tracing::error!("Unhandled event type: {:?}", event); - } - } - } - } - } - } - } - } - Err(e) => { - tracing::error!("Error while receiving event: {:?}", e); - } - } - }, - else => break, - } - } - } - - async fn handle_ibc_event( - &self, - ibc_event: IbcEvent, - event_state_map: &EventStateMap, - block_number: u64, - tx_hash: Option, - ) { - IbcListen::handle_ibc_event_boxed(self, ibc_event, event_state_map, block_number, tx_hash) - .await; - } -} - -impl IbcTransfer for Ethereum { - async fn send_ibc_transfer( - &self, - protocol: Protocol, - channel: ChannelId, - destination_channel: ChannelId, - denom: String, - amount: u64, - memo: String, - max_retry: u64, - ) { - let mut rng = StdRng::from_entropy(); - let index = rng.gen_range(0..self.relays.len()); - let relay = &self.relays[index]; - let signer_middleware = self.signer_middlewares[index].lock().await; - let msg_sender = self.msg_senders[index]; - - let denom_address = match ethers::types::H160::from_str(&denom) { - Ok(address) => address, - Err(_) => { - let formatted_denom = format!( - "{}/{}/{}", - self.ucs01_contract.to_lowercase(), - destination_channel, - denom - ); - - relay - .get_denom_address( - destination_channel.clone().to_string(), - formatted_denom.clone(), - ) - .call() - .await - .unwrap() - } - }; - - if denom_address == ethers::types::H160::zero() { - tracing::error!("Denom address not found"); - return; - } - let erc_contract = erc20::ERC20::new(denom_address, signer_middleware.clone()); - let balance = erc_contract.balance_of(msg_sender).await.unwrap(); - tracing::info!( - "ETH Token({:?}) balance: {} of account: {:?}. Sending amount: {}", - denom_address, - balance, - msg_sender, - amount - ); - - if balance < ethers::types::U256::exp10(9) { - tracing::error!( - "[INSUFFICIENT ERC20 BALANCE] Current balance is: {}. It should always be higher than: {}. Address: {:?}, ERC20 Address: {:?}", - balance, - ethers::types::U256::exp10(9).to_string(), - msg_sender, - erc_contract - ); - } - - if balance < amount.into() { - tracing::warn!( - "Insufficient balance: {}. Requested amount: {}", - balance, - amount - ); - return; - } - - let allowance = erc_contract - .allowance(msg_sender, self.relay_addr) - .await - .unwrap(); - if allowance < amount.into() { - let _ = erc_contract - .approve(self.relay_addr, (U256::MAX / U256::from(2)).into()) - .send() - .await; - tracing::info!("We can not transfer after approve, returning now."); - return; - } - - let mut debug_msg; - match protocol { - Protocol::Ucs01 { - ref receivers, - ref contract, - } => { - let mut rng = StdRng::from_entropy(); - let index = rng.gen_range(0..receivers.len()); // Select a random index - - let receiver = &receivers[index]; - - let mut final_receiver = receiver.encode_to_vec().into(); - - if memo.is_empty() { - let (_hrp, data, _variant) = - bech32::decode(&receiver).expect("Invalid Bech32 address"); - - let bytes: Vec = - Vec::::from_base32(&data).expect("Invalid base32 data"); - - final_receiver = bytes.into(); - } - - debug_msg = format!( - "[Ethereum] -> Sent IBC transfer. memo: {}. Sending denom: {}. To: {:?}. Amount: {}, contract: {}, from {:?}", - memo, - denom, - final_receiver, - amount, - contract, - msg_sender - ); - - let tx_rcp: Option = match relay - .send( - destination_channel.clone().to_string(), - final_receiver, - [LocalToken { - denom: denom_address, - amount: amount as u128, - }] - .into(), - memo.clone(), - (Height { - revision_number: 0, - revision_height: 0, - }) - .into(), - u64::MAX, - ) - .send() - .await - { - Ok(response) => match response.await { - Ok(receipt) => Some(receipt.expect("Failed to get transaction receipt")), - Err(e) => { - tracing::error!("Failed to get transaction receipt: {:?}", e); - return; - } - }, - Err(ethers::contract::ContractError::MiddlewareError { e }) => { - if e.to_string().contains("replacement transaction underprice") { - if max_retry == 0 { - tracing::warn!( - "Replacement transaction underprice. No more retrying" - ); - } else { - tracing::info!("Retrying transaction."); - self.send_ibc_transfer( - protocol, - channel, - destination_channel, - denom, - amount, - memo, - max_retry - 1, - ); - } - } else { - tracing::error!( - "MiddlewareError Failed to send transaction eth->union: {:?}", - e.to_string() - ); - } - return; - } - Err(e) => { - if e.to_string().contains("nonce too low") - || e.to_string() - .contains("Contract call reverted with data: 0x") - { - if max_retry == 0 { - tracing::warn!("No more retrying"); - } else { - tracing::info!( - "Retrying transaction. msg_sender: {:?}", - msg_sender - ); - self.send_ibc_transfer( - protocol, - channel, - destination_channel, - denom, - amount, - memo, - max_retry - 1, - ); - } - } else { - tracing::error!("Failed to send transaction eth->union: {:?}", e); - } - return; - } - }; - let tx_hash = format!("{:?}", tx_rcp.unwrap().transaction_hash); - debug_msg.push_str(&format!(" Tx Hash: {}", tx_hash)); - - tracing::info!(debug_msg); - } - Protocol::Ics20 { - receivers: _, - module: _, - } => { - unimplemented!("Ics20 protocol not implemented"); // TODO: Do we even have this case? - } - } - } -} - -impl Ethereum { - pub async fn new(config: EthereumConfig) -> Self { - let ethereum_rpc = EthereumRpc { - provider: Arc::new(Provider::new( - Ws::connect(config.eth_rpc_api.clone()).await.unwrap(), - )), - ibc_handler_address: config.ibc_handler_address, - }; - - let mut relays = Vec::new(); - let mut signers_middleware = Vec::new(); - let mut msg_senders = Vec::new(); - - let (relay_addr, ucs01_contract) = match config.transfer_module { - TransferModule::Contract { ref address } => { - let relay_addr: Address = address.parse().expect("Invalid contract address"); - (relay_addr, address.clone()) - } - TransferModule::Native => { - panic!("Native transfer module is not supported in this context") - } - }; - for signer in config.signers.clone() { - let signing_key: ecdsa::SigningKey = signer.value(); - let address_of_privkey: ethers::types::H160 = secret_key_to_address(&signing_key); - tracing::info!("address: {:?}", address_of_privkey); - - let provider: Arc> = ethereum_rpc.provider.clone(); - - let chain_id = provider - .get_chainid() - .await - .expect("Failed to get chain ID") - .as_u64(); - let wallet = LocalWallet::new_with_signer(signing_key, address_of_privkey, chain_id); - - let signer_middleware = Arc::new(SignerMiddleware::new( - NonceManagerMiddleware::new(provider.clone(), address_of_privkey), - wallet.clone(), - )); - - let relay = UCS01Relay::new(relay_addr, signer_middleware.clone()); - - relays.push(relay); - signers_middleware.push(tokio::sync::Mutex::new(signer_middleware)); - msg_senders.push(address_of_privkey); - } - Ethereum { - rpc: ethereum_rpc, - relays, - signer_middlewares: Arc::new(signers_middleware), - ucs01_contract, - msg_senders, - relay_addr, - } - } -} - -#[derive(Debug, Clone)] -pub struct Cosmos { - pub chain: chain_utils::cosmos::Cosmos, -} - -#[derive(Debug, Clone)] -pub struct Union { - pub chain: chain_utils::union::Union, -} - -impl IbcTransfer for Cosmos { - async fn send_ibc_transfer( - &self, - protocol: Protocol, - channel: ChannelId, - destination_channel: ChannelId, - denom: String, - amount: u64, - memo: String, - max_retry: u64, - ) { - self.chain.keyring.with(|signer| async move { - let mut debug_msg; - let transfer_msg = match protocol { - Protocol::Ics20 { ref receivers, ref module } => { - let mut rng = StdRng::from_entropy(); - - let receiver = match receivers.choose(&mut rng) { - Some(receiver) => receiver, - None => { - tracing::error!("No receiver found."); - return; - } - }; - - let msg = MsgTransfer { - source_port: "transfer".into(), - source_channel: destination_channel.to_string(), - token: Some( - (Coin { - denom: denom.to_string(), - amount: amount as u128, - }).into() - ), - sender: signer.to_string(), - receiver: receiver.to_string(), - timeout_height: None, - timeout_timestamp: u64::MAX / 2, - memo: memo.clone(), - }; - - debug_msg = format!( - "[Cosmos Ics20] -> SENT IBC transfer from: {}. memo: {}. denom: {}. To: {}. Amount: {}, module: {:?}", - signer.to_string(), - memo, - denom, - receiver, - amount, - module - ); - - Any { - type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), - value: msg.encode_to_vec().into(), - } - } - Protocol::Ucs01 { ref receivers, ref contract } => { - let mut rng = StdRng::from_entropy(); - let receiver = match receivers.choose(&mut rng) { - Some(receiver) => receiver, - None => { - tracing::error!("No receiver found."); - return; - } - }; - - let transfer_msg = ExecuteMsg::Transfer(TransferMsg { - channel: destination_channel.to_string(), - receiver: receiver[2..].to_string(), - memo: memo.clone(), - timeout: None, - }); - - // TODO(caglankaan): This part is not clear right now. For the first version - // It would be better to get denom directly as smth like - // `factory/union1m37cxl0ld4uaw3r4lv9nt2uw69xxf8xfjrf7a4w9hamv6xvp6ddqqfaaaa/0xe619529b4396a62ab6d88ff2bb195e83c11e909ad9` - // for USDC for example. The code below works but it would be hard to check if its native or smth else. - // For next version(s) we can add a feature to calculate this with like - // "token": { - // "protocol": "union", - // "type": "native", - // "denom": "muno" - // } - - // let foreign_denom = format!( - // "wasm.{}/{}/{}", - // contract, - // destination_channel.to_string(), - // denom.to_lowercase() - // ); - // let hashed_foreign_denom = hash_denom_str(&foreign_denom); - - // let final_denom = format!( - // "factory/{}/{}", - // contract.to_string(), - // hashed_foreign_denom - // ); - - let transfer_msg_bytes = serde_json::to_vec(&transfer_msg).unwrap(); - - debug_msg = format!( - "[Cosmos] -> SENT IBC transfer from: {}. memo: {}. denom: {}. To: {}. Amount: {}, contract: {}", - signer.to_string(), - memo, - denom, - receiver, - amount, - contract - ); - - any::Any(MsgExecuteContract { - sender: signer.to_string(), - contract: contract.clone(), - msg: transfer_msg_bytes, - funds: vec![Coin { - denom: denom.to_string(), - amount: amount as u128, - }], - }).into() - } - }; - - match self.chain.broadcast_tx_commit(signer, [transfer_msg]).await { - Ok(tx_hash) => { - debug_msg.push_str( - &format!(" Tx Hash: {}", tx_hash.to_string()[2..].to_ascii_uppercase()) - ); - tracing::info!(debug_msg); - } - Err(BroadcastTxCommitError::SimulateTx(e)) => { - if e.contains("account sequence mismatch") { - tracing::warn!("Account sequence mismatch."); - if max_retry == 0 { - tracing::warn!("Account sequence mismatch. No more retrying"); - } else { - tracing::info!("Retrying transaction."); - self.send_ibc_transfer( - protocol, - channel, - destination_channel, - denom, - amount, - memo, - max_retry - 1 - ); - } - } else { - tracing::error!("Failed to simulate tx!{:?}", e.to_string()); - } - } - Err(e) => { - tracing::error!("Failed to submit tx!{:?}", e.to_string()); - } - } - }).await; - } -} - -impl Cosmos { - pub async fn new(config: CosmosConfig) -> Self { - let cosmos = chain_utils::cosmos::Cosmos::new(config.chain_config) - .await - .unwrap(); - - Cosmos { chain: cosmos } - } -} - -async fn get_channel_for_eth_ack_packet( - eth_rpcs: &EthereumRpc, - port_id: String, - channel_id: String, - block_number: u64, -) -> Option { - tracing::info!(port_id, channel_id, block_number); - - let channel_result = eth_rpcs - .ibc_handler() - .get_channel(port_id.clone(), channel_id.clone()) - .block(block_number) - .await; - - match channel_result { - Ok(channel) => channel.try_into().ok(), - Err(e) => { - tracing::error!( - "Failed to fetch channel for port: {}, channel: {}. Error: {}", - port_id, - channel_id, - e - ); - return None; - } - } -} - -async fn ibchandler_events_to_ibc_event( - log: RawLog, - eth_rpcs: &EthereumRpc, - block_number: u64, -) -> Option { - match IBCHandlerEvents::decode_log(&log) { - Ok(event) => { - // Handle the decoded event similarly to Tendermint events - match event { - IBCHandlerEvents::PacketEvent(packet_event) => match packet_event { - IBCPacketEvents::SendPacketFilter(event) => { - if let Some(channel) = get_channel_for_eth_ack_packet( - eth_rpcs, - event.source_port.clone(), - event.source_channel.clone(), - block_number, - ) - .await - { - Some(IbcEvent::SendPacket(SendPacket { - packet_sequence: event.sequence.try_into().unwrap(), - packet_src_port: event.source_port.parse().unwrap(), - packet_src_channel: event.source_channel.parse().unwrap(), - packet_dst_port: channel.counterparty.port_id, - packet_dst_channel: channel - .counterparty - .channel_id - .to_string() - .parse() - .unwrap(), - packet_timeout_height: event.timeout_height.into(), - packet_timeout_timestamp: event.timeout_timestamp, - packet_data_hex: hex_encode(event.data).into(), - packet_channel_ordering: channel.ordering, - connection_id: channel.connection_hops[0].clone(), - })) - } else { - None - } - } - IBCPacketEvents::RecvPacketFilter(event) => { - if let Some(channel) = get_channel_for_eth_ack_packet( - eth_rpcs, - event.packet.destination_port.clone(), - event.packet.destination_channel.clone(), - block_number, - ) - .await - { - tracing::info!("Found channel for packet: {:?}", channel); - Some(IbcEvent::RecvPacket(RecvPacket { - packet_sequence: event.packet.sequence.try_into().unwrap(), - packet_src_port: event.packet.source_port.parse().unwrap(), - packet_src_channel: event.packet.source_channel.parse().unwrap(), - packet_dst_port: event.packet.destination_port.parse().unwrap(), - packet_dst_channel: event - .packet - .destination_channel - .parse() - .unwrap(), - packet_timeout_height: event.packet.timeout_height.into(), - packet_timeout_timestamp: event.packet.timeout_timestamp, - packet_data_hex: hex_encode(event.packet.data).into(), - packet_channel_ordering: channel.ordering, - connection_id: channel.connection_hops[0].clone(), - })) - } else { - tracing::error!( - "Could not find channel for packet: {:?}", - event.packet - ); - None - } - } - IBCPacketEvents::AcknowledgePacketFilter(event) => { - if let Some(channel) = get_channel_for_eth_ack_packet( - eth_rpcs, - event.packet.source_port.clone(), - event.packet.source_channel.clone(), - block_number, - ) - .await - { - Some(IbcEvent::AcknowledgePacket(AcknowledgePacket { - packet_sequence: event.packet.sequence.try_into().unwrap(), - packet_src_port: event.packet.source_port.parse().unwrap(), - packet_src_channel: event.packet.source_channel.parse().unwrap(), - packet_dst_port: event.packet.destination_port.parse().unwrap(), - packet_dst_channel: event - .packet - .destination_channel - .parse() - .unwrap(), - packet_timeout_height: event.packet.timeout_height.into(), - packet_timeout_timestamp: event.packet.timeout_timestamp, - packet_channel_ordering: channel.ordering, - connection_id: channel.connection_hops[0].clone(), - })) - } else { - None - } - } - IBCPacketEvents::WriteAcknowledgementFilter(event) => { - if let Some(channel) = get_channel_for_eth_ack_packet( - eth_rpcs, - event.packet.destination_port.clone(), - event.packet.destination_channel.clone(), - block_number, - ) - .await - { - Some(IbcEvent::WriteAcknowledgement(WriteAcknowledgement { - packet_sequence: event.packet.sequence.try_into().unwrap(), - packet_src_port: event - .packet - .source_port - .to_string() - .parse() - .unwrap(), - packet_src_channel: event - .packet - .destination_channel - .parse() - .unwrap(), - packet_dst_port: event.packet.destination_port.parse().unwrap(), - packet_dst_channel: event - .packet - .destination_channel - .parse() - .unwrap(), - packet_timeout_height: Height { - revision_number: 0, - revision_height: 0, - }, - packet_ack_hex: event.acknowledgement.to_vec(), - packet_data_hex: hex_encode(event.packet.data).into(), - packet_timeout_timestamp: 0, - connection_id: channel.connection_hops[0].clone(), - })) - } else { - None - } - } - _ => { - tracing::warn!("Unhandled packet event type."); - None - } - }, - _ => { - tracing::warn!("Unhandled event type."); - None - } - } - } - Err(_) => { - tracing::warn!("Could not decode Ethereum log event."); - None - } - } -} diff --git a/sentinel/src/config.rs b/sentinel/src/config.rs deleted file mode 100644 index ae0ca29eec..0000000000 --- a/sentinel/src/config.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::collections::HashMap; - -use chain_utils::private_key::PrivateKey; -use chrono::{DateTime, Utc}; -use ethers::{core::k256::ecdsa, types::H256}; -use serde::{Deserialize, Serialize}; -use unionlabs::{hash::H160, id::ChannelId}; - -use crate::chains::Protocol; - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Config { - pub chain_configs: HashMap, - pub interactions: Vec, - pub single_interaction: Option, // This is just to send single transaction and close the program -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct EventTrackerConfig { - pub idx: u64, // 0: sendpacket, 1: recvpacket, 2:writeack, 3:acknowledge - pub arrived: bool, // is packet arrived or not - pub arrived_time: Option>, // time when packet arrived - pub tx_hash: Option, // hash of the transaction -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct EthereumConfig { - pub ibc_handler_address: H160, - pub eth_rpc_api: String, - pub transfer_module: TransferModule, - pub signers: Vec>, - pub enabled: bool, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct CosmosConfig { - pub chain_config: chain_utils::cosmos::Config, - pub transfer_module: TransferModule, - pub enabled: bool, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum TransferModule { - Native, - Contract { address: String }, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct IbcInteraction { - pub source: Endpoint, - pub destination: Endpoint, - pub send_packet_interval: u64, - pub expect_full_cycle: u64, - pub protocol: Protocol, - pub amount_min: u64, - pub amount_max: u64, - pub memo: String, - pub sending_memo_probability: f64, - pub denoms: Vec, - pub max_retry: u64, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Endpoint { - pub chain: String, - pub channel: ChannelId, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(rename_all = "snake_case")] -pub enum AnyChainConfig { - Cosmos(CosmosConfig), - Ethereum(EthereumConfig), -} diff --git a/sentinel/src/context.rs b/sentinel/src/context.rs deleted file mode 100644 index 6e39dcf118..0000000000 --- a/sentinel/src/context.rs +++ /dev/null @@ -1,410 +0,0 @@ -use std::{collections::HashMap, str::FromStr, sync::Arc, time::Duration}; - -use contracts::erc20; -use dashmap::DashMap; -use ethers::{ - providers::{Middleware, Provider, Ws}, - types::U256, -}; -use parking_lot::Mutex; -use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; -use tokio::{task::JoinHandle, time::interval}; - -use crate::{ - chains::{Chain, Cosmos, Ethereum, IbcListen as _, IbcTransfer as _}, - config::{AnyChainConfig, Config, EventTrackerConfig, IbcInteraction}, -}; - -pub type EventStateMap = Arc>>>; - -#[derive(Clone, Debug)] -pub struct Context { - pub chains: HashMap, - pub interactions: Vec, - pub event_state_map: EventStateMap, -} - -impl Context { - pub async fn new(config: Config) -> Result { - let mut chains = HashMap::new(); - - for (chain_name, chain) in config.chain_configs { - match chain { - AnyChainConfig::Cosmos(cosmos) if cosmos.enabled => { - tracing::info!("Initializing Cosmos chain: {}", chain_name); - chains.insert(chain_name, Chain::Cosmos(Cosmos::new(cosmos).await)); - } - AnyChainConfig::Ethereum(ethereum) if ethereum.enabled => { - tracing::info!("Initializing Ethereum chain: {}", chain_name); - chains.insert(chain_name, Chain::Ethereum(Ethereum::new(ethereum).await)); - } - _ => { - // Handle cases where the chain is not enabled or other configurations - } - } - } - - tracing::info!( - "Initialized chains: {:?}", - chains.keys().collect::>() - ); - - // Initialize the shared hashmap - let event_state_map = Arc::new(DashMap::new()); - - Ok(Self { - chains, - interactions: config.interactions, - event_state_map, // Set the event_state_map field - }) - } - - pub async fn listen(&self) -> Vec> { - let mut handles = vec![]; - - for (chain_name, chain) in &self.chains { - tracing::info!(%chain_name, "listening on chain"); - - let event_state_map = self.event_state_map.clone(); - let chain = chain.clone(); - - let handle = tokio::spawn(async move { - chain.listen(&event_state_map).await; - }); - handles.push(handle); - } - handles - } - - pub async fn do_single_transaction(&self, interaction: IbcInteraction) -> JoinHandle<()> { - let source_chain = self.chains.get(&interaction.source.chain).cloned().unwrap(); - - tokio::spawn(async move { - let mut interval = interval(Duration::from_secs(interaction.send_packet_interval)); - - interval.tick().await; - let mut rng = StdRng::from_entropy(); - - let amount = rng.gen_range(interaction.amount_min..=interaction.amount_max); - - let send_memo = rng.gen_bool(interaction.sending_memo_probability); - let memo = if send_memo { - interaction.memo.clone() - } else { - String::new() - }; - - // selecting denom randomly - let denom = interaction.denoms.choose(&mut rng).unwrap().to_string(); - - match &source_chain { - Chain::Ethereum(ethereum) => { - ethereum - .send_ibc_transfer( - interaction.protocol.clone(), - interaction.source.channel.clone(), - interaction.destination.channel.clone(), - denom, - amount, - memo, - interaction.max_retry, - ) - .await; - } - Chain::Cosmos(cosmos) => { - cosmos - .send_ibc_transfer( - interaction.protocol.clone(), - interaction.source.channel.clone(), - interaction.destination.channel.clone(), - denom, - amount, - memo, - interaction.max_retry, - ) - .await; - } - } - }) - } - - pub async fn check_balances(self) -> Vec> { - let min_eth_balance = U256::exp10(17); // 0.1 ETH - // let mut min_erc20_balances = HashMap::new(); - // min_erc20_balances.insert( - // "0x1c7d4b196cb0c7b01d743fbc6116a902379c7238".to_string(), // todo change to actual token address. - // U256::exp10(9) // 1000 Token - // ); // 1 token - - let mut handles = vec![]; - - handles.push( - self.check_ethereum_balances(min_eth_balance /*min_erc20_balances*/) - .await, - ); - - handles - } - - pub async fn check_ethereum_balances( - &self, - min_balance: U256, /*min_erc20_balances: HashMap*/ - ) -> JoinHandle<()> { - let ethereum_chains: Vec<_> = self - .chains - .values() - .filter_map(|chain| { - if let Chain::Ethereum(eth) = chain { - Some(eth) - } else { - None - } - }) - .cloned() - .collect(); - - tokio::spawn(async move { - let mut interval = interval(Duration::from_secs(600)); // TODO: Make this configurable (?) - - loop { - interval.tick().await; - for ethereum in ðereum_chains { - for signer_middleware in &*ethereum.signer_middlewares { - let signer_middleware = signer_middleware.lock().await; - - let address = signer_middleware.address(); - let provider: Arc> = ethereum.rpc.provider.clone(); - - let chain_id = provider - .get_chainid() - .await - .expect("Failed to get chain ID") - .as_u64(); - - // Check native balance - match provider.get_balance(address, None).await { - Ok(balance) => { - if balance < min_balance { - tracing::error!( - "[INSUFFICIENT BALANCE] Insufficient native balance for address {:?}. Balance: {}, Required: {}. Chain ID: {}", - address, - balance, - min_balance, - chain_id - ); - } else { - tracing::info!( - "Sufficient ETH balance for address {:?}. Balance: {}, Required: {}", - address, - balance, - min_balance - ); - } - } - Err(e) => - tracing::error!( - "Error checking native balance for address {:?}. Required: {}. Error: {:?}", - address, - min_balance, - e - ), - } - // TODO (caglankaan): I think checking them here is not necessary and makes thing more complicated - // We can check the erc20 balances in the send_ibc message anyway, right? - - // Check ERC20 balances - // for (erc20_address, min_balance) in &min_erc20_balances { - // let erc20_contract = erc20::ERC20::new( - // ethers::types::H160 - // ::from_str(erc20_address) - // .expect("Failed to parse transaction hash"), - // signer_middleware.clone() - // ); - // match erc20_contract.balance_of(address).await { - // Ok(balance) => { - // if balance < *min_balance { - // tracing::error!( - // "[INSUFFICIENT BALANCE] Insufficient ERC20 balance for token {} on address {}. Balance: {}, Required: {}", - // erc20_address, - // address, - // balance, - // min_balance - // ); - // } else { - // tracing::info!( - // "Sufficient ERC20 balance for token {} on address {}. Balance: {}, Required: {}", - // erc20_address, - // address, - // balance, - // min_balance - // ); - // } - // } - // Err(e) => - // tracing::error!( - // "Error checking ERC20 balance for token {} on address {}. Required: {}. Error: {:?}", - // erc20_address, - // address, - // min_balance, - // e - // ), - // } - // } - } - } - } - }) - } - - pub async fn do_transactions(self) -> Vec> { - let mut handles = vec![]; - - for interaction in self.interactions { - let source_chain = self.chains.get(&interaction.source.chain).cloned().unwrap(); - let _destination_chain = self - .chains - .get(&interaction.destination.chain) - .cloned() - .unwrap(); - - let handle = tokio::spawn(async move { - let mut interval = interval(Duration::from_secs(interaction.send_packet_interval)); - - loop { - interval.tick().await; - let mut rng = StdRng::from_entropy(); - - let amount = rng.gen_range(interaction.amount_min..=interaction.amount_max); - - // Determine memo based on sending_memo_probability - let send_memo = rng.gen_bool(interaction.sending_memo_probability); - let memo = if send_memo { - interaction.memo.clone() - } else { - String::new() - }; - - // selecting denom randomly - let denom = interaction.denoms.choose(&mut rng).unwrap().to_string(); - - match &source_chain { - Chain::Ethereum(ethereum) => { - ethereum - .send_ibc_transfer( - interaction.protocol.clone(), - interaction.source.channel.clone(), - interaction.destination.channel.clone(), - denom, - amount, - memo, - interaction.max_retry, - ) - .await; - } - Chain::Cosmos(cosmos) => { - cosmos - .send_ibc_transfer( - interaction.protocol.clone(), - interaction.source.channel.clone(), - interaction.destination.channel.clone(), - denom, - amount, - memo, - interaction.max_retry, - ) - .await; - } - } - } - }); - handles.push(handle); - } - handles - } - pub async fn check_packet_sequence(&self, expect_full_cycle: u64, key: &str) -> JoinHandle<()> { - let event_state_map = Arc::clone(&self.event_state_map); - let key = key.to_string(); // Clone the key to extend its lifetime - - tokio::spawn(async move { - let mut interval = interval(Duration::from_secs(10)); // TODO: Make this configurable (?) - - loop { - interval.tick().await; - - // let mut event_state_map = event_state_map.lock().await; - - if let Some(mut inner_map) = event_state_map.get_mut(&key) { - // Use get_mut to get a mutable reference - let sequences_to_remove: Vec = inner_map - .iter() - .filter_map(|(&sequence, event_map)| { - let mut all_events_received = true; - for event_data in event_map.values() { - if !event_data.arrived { - all_events_received = false; - } - } - - if all_events_received { - tracing::info!("All events received for sequence: {}", sequence); - Some(sequence) - } else { - if let Some(event_data) = event_map.get(&0) { - if let Some(send_packet_time) = event_data.arrived_time { - let now = chrono::Utc::now(); - let duration = now.signed_duration_since(send_packet_time); - - if duration.num_seconds() >= (expect_full_cycle as i64) { - tracing::error!( - "[TRANSFER FAILED] Not all events received for sequence: {} after {} seconds. Event map: {:?}. Removing due to timeout.", - sequence, - duration.num_seconds(), - event_map - ); - Some(sequence) - } else { - None - } - } else { - tracing::error!( - "Not all events received for sequence: {} and no SendPacket timestamp found", - sequence - ); - Some(sequence) - } - } else { - tracing::error!( - "SendPacket event not found for sequence: {}", - sequence - ); - None - } - } - }) - .collect(); - - // Remove sequences with all events received - for sequence in sequences_to_remove { - inner_map.remove(&sequence); - tracing::info!("Removed sequence: {} from chain flow: {}", sequence, key); - } - } - } - }) - } - - pub async fn check_packet_sequences(&self) -> Vec> { - let mut handles = vec![]; - for interaction in &self.interactions { - let key = format!( - "{}->{}", - interaction.destination.channel, interaction.source.channel - ); - let expect_full_cycle = interaction.expect_full_cycle; - tracing::info!("Calling check_packet_sequence for key: {}", key); - let handle = self.check_packet_sequence(expect_full_cycle, &key).await; - handles.push(handle); - } - handles - } -} diff --git a/sentinel/src/main.rs b/sentinel/src/main.rs deleted file mode 100644 index 1dc56a7b93..0000000000 --- a/sentinel/src/main.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::{ffi::OsString, fs}; - -use clap::Parser; -use config::Config; -use context::Context; -use futures::future::try_join_all; -use tokio::signal; -use tracing_subscriber::EnvFilter; - -pub mod chains; -pub mod config; -pub mod context; - -#[derive(Debug, Clone, PartialEq, Default, clap::ValueEnum, derive_more::Display)] -pub enum LogFormat { - #[default] - #[display(fmt = "text")] - Text, - #[display(fmt = "json")] - Json, -} - -/// Arguments provided to the top-level command. -#[derive(Debug, Parser, Clone)] -pub struct AppArgs { - /// The path to the configuration file. - #[arg( - long, - short = 'c', - global = true, - default_value = "sentinel-config.json" - )] - pub config: OsString, - - /// Disable the listen functionality. - #[arg(long, global = true)] - pub no_listen: bool, - - /// Disable the interaction functionality. - #[arg(long, global = true)] - pub no_interaction: bool, - - /// Perform a single interaction from the provided config. - #[arg(long, global = true)] - pub single_interaction: bool, - - #[arg(long, short = 'l', env, global = true, default_value_t = LogFormat::default())] - pub log_format: LogFormat, - - // Check balances - #[arg(long, global = true)] - pub check_balances: bool, -} - -#[tokio::main(flavor = "multi_thread")] -async fn main() { - let args = AppArgs::parse(); - let config: Config = serde_json::from_str(&fs::read_to_string(args.config).unwrap()).unwrap(); - - match args.log_format { - LogFormat::Text => { - tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .init(); - } - LogFormat::Json => { - tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env()) - .json() - .init(); - } - } - - let context = Context::new(config.clone()).await.unwrap(); - - let mut handles = vec![]; - - if args.single_interaction { - if let Some(single_interaction) = config.single_interaction.clone() { - handles.push( - context - .clone() - .do_single_transaction(single_interaction) - .await, - ); - } else { - tracing::error!( - "Single transaction flag provided, but no single_transaction configuration found." - ); - std::process::exit(1); - } - } else { - if !args.no_interaction { - handles.extend(context.clone().do_transactions().await); - } - - if !args.no_listen { - tracing::info!("Starting listen functionality"); - handles.extend(context.clone().listen().await); - handles.extend(context.clone().check_packet_sequences().await); - } else { - tracing::info!("Listen functionality disabled"); - } - } - - if args.check_balances { - handles.extend(context.clone().check_balances().await); - } - - // Await all handles and handle panics - let result: Result, _> = try_join_all(handles).await; - if let Err(e) = result { - tracing::error!("A task has panicked: {:?}", e); - std::process::exit(1); - } - - if !args.single_interaction { - signal::ctrl_c().await.unwrap(); - } -}