diff --git a/SECURITY.md b/SECURITY.md index 2bb4fe6f8e..937e5d144e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -54,7 +54,7 @@ The Wormhole project operates a bug bounty program to financially incentivize in - [Immunefi-Hosted Program](https://immunefi.com/bounty/wormhole/) - **Scopes**: Guardian and Smart Contracts - - **Rewards**: Up to $2,500,000 USDC + - **Rewards**: Up to $5,000,000 USDC - **KYC**: Required If you find a security issue in Wormhole, please report the issue immediately using the bug bounty program above. diff --git a/clients/js/src/consts/networks.ts b/clients/js/src/consts/networks.ts index 3bf6f7b70c..63c3b729bc 100644 --- a/clients/js/src/consts/networks.ts +++ b/clients/js/src/consts/networks.ts @@ -26,7 +26,7 @@ const Mainnet = { key: getEnvVar("TERRA_MNEMONIC"), }, Ethereum: { - rpc: `https://rpc.ankr.com/eth`, + rpc: `https://ethereum-rpc.publicnode.com`, key: getEnvVar("ETH_KEY"), chain_id: 1, }, diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index 2c3fc20235..1a4f2f1567 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -2597,8 +2597,11 @@ dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", "cw-storage-plus 0.13.4", "semver", + "serde", + "serde-json-wasm 0.4.1", "serde_wormhole", "thiserror", "wormhole-bindings", diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml b/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml index 778fe83edb..58c6be3054 100644 --- a/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml +++ b/cosmwasm/contracts/wormchain-ibc-receiver/Cargo.toml @@ -21,3 +21,9 @@ thiserror = "1.0.31" wormhole-bindings = "0.1.0" wormhole-sdk = { workspace = true, features = ["schemars"] } serde_wormhole.workspace = true + +[dev-dependencies] +cw-multi-test = "0.13.2" +serde-json-wasm = "0.4" +wormhole-bindings = { version = "0.1.0", features=["fake"] } +serde = { version = "1.0.137", default-features = false, features = ["derive"] } \ No newline at end of file diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs index 10d266d8f4..31d3febfdc 100644 --- a/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/lib.rs @@ -3,3 +3,6 @@ pub mod error; pub mod ibc; pub mod msg; pub mod state; + +#[cfg(test)] +pub mod tests; diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs new file mode 100644 index 0000000000..86210bfe20 --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/integration_tests.rs @@ -0,0 +1,362 @@ +use crate::{ + contract::{execute, query}, + msg::{AllChannelChainsResponse, ExecuteMsg, QueryMsg}, + tests::test_utils::{create_gov_vaa_body, create_transfer_vaa_body, sign_vaa_body}, +}; +use anyhow::Error; +use cosmwasm_std::{ + from_binary, + testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, + to_binary, Binary, ContractResult, Deps, DepsMut, Empty, QuerierWrapper, SystemResult, +}; +use wormhole_bindings::{fake::WormholeKeeper, WormholeQuery}; +use wormhole_sdk::{ + ibc_receiver::{Action, GovernancePacket}, + vaa::Body, + Chain, GOVERNANCE_EMITTER, +}; + +#[test] +pub fn add_channel_chain_happy_path() -> anyhow::Result<(), Error> { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + let env = mock_env(); + + let add_sei_channel_body = create_gov_vaa_body(1, Chain::Sei, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_sei_vaa_binary) = sign_vaa_body(wh.clone(), add_sei_channel_body); + + let submissions = execute( + mut_deps.branch(), + env.clone(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_sei_vaa_binary], + }, + ); + + assert!( + submissions.is_ok(), + "A proper UpdateChannelChain gov vaa should be accepted" + ); + + // create a readonly deps to use for querying the state + let empty_mock_querier = MockQuerier::::new(&[]); + let readonly_deps = Deps { + storage: mut_deps.storage, + api: mut_deps.api, + querier: QuerierWrapper::new(&empty_mock_querier), + }; + + let channel_binary = query(readonly_deps, env, QueryMsg::AllChannelChains {})?; + let channel: AllChannelChainsResponse = from_binary(&channel_binary)?; + + assert_eq!(channel.channels_chains.len(), 1); + let channel_entry = channel.channels_chains.first().unwrap(); + assert_eq!( + channel_entry.0, + Binary::from(*b"channel-0"), + "the stored channel for sei should initially be channel-0" + ); + assert_eq!( + channel_entry.1, + Into::::into(Chain::Sei), + "the stored channel should be for sei's chain id" + ); + + Ok(()) +} + +#[test] +pub fn add_channel_chain_happy_path_multiple() -> anyhow::Result<(), Error> { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + + let add_inj_channel_body = create_gov_vaa_body(2, Chain::Injective, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-1"); + let (_, add_inj_vaa_bin) = sign_vaa_body(wh.clone(), add_inj_channel_body); + let add_sei_channel_body = create_gov_vaa_body(3, Chain::Sei, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-2"); + let (_, add_sei_vaa_binary) = sign_vaa_body(wh.clone(), add_sei_channel_body); + + // add a channel for injective and update the channel set for sei + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_sei_vaa_binary, add_inj_vaa_bin], + }, + ); + + assert!( + submissions.is_ok(), + "A pair of proper UpdateChannelChain gov vaas should be accepted" + ); + + // create a readonly deps to use for querying the state + let empty_mock_querier = MockQuerier::::new(&[]); + let readonly_deps = Deps { + storage: mut_deps.storage, + api: mut_deps.api, + querier: QuerierWrapper::new(&empty_mock_querier), + }; + + // refetch all the channels that are in state + let channel_binary = query(readonly_deps, mock_env(), QueryMsg::AllChannelChains {})?; + let AllChannelChainsResponse { + channels_chains: mut channels, + }: AllChannelChainsResponse = from_binary(&channel_binary)?; + + channels.sort_by(|(_, a_chain_id), (_, b_chain_id)| a_chain_id.cmp(b_chain_id)); + + assert_eq!(channels.len(), 2); + + let channel_entry = channels.first().unwrap(); + assert_eq!( + channel_entry.0, + Binary::from(*b"channel-1"), + "the stored channel should be channel-1 " + ); + assert_eq!( + channel_entry.1, + Into::::into(Chain::Injective), + "the stored channel should be for injective's chain id" + ); + + let channel_entry = channels.last().unwrap(); + assert_eq!( + channel_entry.0, + Binary::from(*b"channel-2"), + "the stored channel should be channel-2" + ); + assert_eq!( + channel_entry.1, + Into::::into(Chain::Sei), + "the stored channel should be for sei's chain id" + ); + + Ok(()) +} + +#[test] +pub fn reject_invalid_add_channel_chain_vaas() { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + + let add_channel_body = create_gov_vaa_body(1, Chain::Wormchain, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!( + submissions.is_err(), + "Cannot add a channel from Gateway to Gateway" + ); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![Binary::from(vec![0u8; 32])], + }, + ); + + assert!( + submissions.is_err(), + "VAA should be rejected if it cannot be parsed because it's too short" + ); + + let add_channel_body = create_transfer_vaa_body(1); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!(submissions.is_err(), "Can only execute governance vaas"); + + let add_channel_body = create_gov_vaa_body(1, Chain::Osmosis, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!( + submissions.is_ok(), + "Can add a channel from Osmosis to Gateway" + ); + + let add_channel_body: Body = Body { + timestamp: 1u32, + nonce: 1u32, + emitter_chain: Chain::Solana, + emitter_address: GOVERNANCE_EMITTER, + sequence: 1u64, + consistency_level: 0, + payload: GovernancePacket { + chain: Chain::Osmosis, + action: Action::UpdateChannelChain { + channel_id: *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0", + chain_id: Chain::CosmosHub, + }, + }, + }; + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!( + submissions.is_err(), + "Cannot add a update a chain besides Gateway" + ); +} + +#[test] +pub fn reject_replayed_add_channel_chain_vaas() { + let wh = WormholeKeeper::new(); + + let querier: MockQuerier = + MockQuerier::new(&[]).with_custom_handler(|q| match q { + WormholeQuery::VerifyVaa { vaa } => { + match WormholeKeeper::new().verify_vaa(&vaa.0, 0u64) { + Ok(_) => SystemResult::Ok(if let Ok(data) = to_binary(&Empty {}) { + ContractResult::Ok(data) + } else { + ContractResult::Err("Unable to convert to binary".to_string()) + }), + Err(e) => SystemResult::Ok(ContractResult::Err(e.to_string())), + } + } + _ => cosmwasm_std::SystemResult::Ok(cosmwasm_std::ContractResult::Ok( + to_binary(&Empty {}).unwrap(), + )), + }); + + let mut mut_deps = DepsMut { + storage: &mut MockStorage::default(), + api: &MockApi::default(), + querier: QuerierWrapper::new(&querier), + }; + let info = mock_info("sender", &[]); + + let add_channel_body = create_gov_vaa_body(1, Chain::Osmosis, *b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00channel-0"); + let (_, add_vaa_binary) = sign_vaa_body(wh.clone(), add_channel_body); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary.clone()], + }, + ); + + assert!( + submissions.is_ok(), + "Can add a channel from Osmosis to Gateway" + ); + + let submissions = execute( + mut_deps.branch(), + mock_env(), + info.clone(), + ExecuteMsg::SubmitUpdateChannelChain { + vaas: vec![add_vaa_binary], + }, + ); + + assert!(submissions.is_err(), "Cannot replay the same VAA"); +} diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs new file mode 100644 index 0000000000..7e5f6c1060 --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/mod.rs @@ -0,0 +1,2 @@ +pub mod integration_tests; +pub mod test_utils; diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs new file mode 100644 index 0000000000..702d83bf7c --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/tests/test_utils.rs @@ -0,0 +1,66 @@ +use cosmwasm_std::{Binary, Uint256}; +use serde::Serialize; +use wormhole_bindings::fake::WormholeKeeper; +use wormhole_sdk::{ + ibc_receiver::{Action, GovernancePacket}, + token::Message, + vaa::{Body, Header, Vaa}, + Address, Amount, Chain, GOVERNANCE_EMITTER, +}; + +pub fn create_transfer_vaa_body(i: usize) -> Body { + Body { + timestamp: i as u32, + nonce: i as u32, + emitter_chain: (i as u16).into(), + emitter_address: Address([(i as u8); 32]), + sequence: i as u64, + consistency_level: 32, + payload: Message::Transfer { + amount: Amount(Uint256::from(i as u128).to_be_bytes()), + token_address: Address([(i + 1) as u8; 32]), + token_chain: (i as u16).into(), + recipient: Address([i as u8; 32]), + recipient_chain: ((i + 2) as u16).into(), + fee: Amount([0u8; 32]), + }, + } +} + +pub fn create_gov_vaa_body( + i: usize, + chain_id: Chain, + channel_id: [u8; 64], +) -> Body { + Body { + timestamp: i as u32, + nonce: i as u32, + emitter_chain: Chain::Solana, + emitter_address: GOVERNANCE_EMITTER, + sequence: i as u64, + consistency_level: 0, + payload: GovernancePacket { + chain: Chain::Wormchain, + action: Action::UpdateChannelChain { + channel_id, + chain_id, + }, + }, + } +} + +pub fn sign_vaa_body(wh: WormholeKeeper, body: Body

) -> (Vaa

, Binary) { + let data = serde_wormhole::to_vec(&body).unwrap(); + let signatures = WormholeKeeper::new().sign(&data); + + let header = Header { + version: 1, + guardian_set_index: wh.guardian_set_index(), + signatures, + }; + + let v = (header, body).into(); + let data = serde_wormhole::to_vec(&v).map(From::from).unwrap(); + + (v, data) +} diff --git a/cspell-custom-words.txt b/cspell-custom-words.txt index 526f4b5a81..c8410ef40a 100644 --- a/cspell-custom-words.txt +++ b/cspell-custom-words.txt @@ -171,6 +171,7 @@ structs subdenom Subdenoms subdenoms +submessage supermajority superminority Tendermint @@ -211,4 +212,4 @@ wormscanurl xlayer XPLA xpla -Zellic +Zellic \ No newline at end of file diff --git a/node/pkg/governor/mainnet_chains.go b/node/pkg/governor/mainnet_chains.go index e6e4dc597c..c2dce4defe 100644 --- a/node/pkg/governor/mainnet_chains.go +++ b/node/pkg/governor/mainnet_chains.go @@ -12,33 +12,33 @@ func chainList() []chainConfigEntry { return []chainConfigEntry{ {emitterChainID: vaa.ChainIDSolana, dailyLimit: 25_000_000, bigTransactionSize: 2_500_000}, {emitterChainID: vaa.ChainIDEthereum, dailyLimit: 50_000_000, bigTransactionSize: 5_000_000}, - {emitterChainID: vaa.ChainIDTerra, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDTerra, dailyLimit: 150_000, bigTransactionSize: 15_000}, {emitterChainID: vaa.ChainIDBSC, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, {emitterChainID: vaa.ChainIDPolygon, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, {emitterChainID: vaa.ChainIDAvalanche, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, - {emitterChainID: vaa.ChainIDOasis, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDOasis, dailyLimit: 250_000, bigTransactionSize: 25_000}, {emitterChainID: vaa.ChainIDAlgorand, dailyLimit: 1_000_000, bigTransactionSize: 100_000}, - {emitterChainID: vaa.ChainIDAurora, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDAurora, dailyLimit: 0, bigTransactionSize: 0}, {emitterChainID: vaa.ChainIDFantom, dailyLimit: 500_000, bigTransactionSize: 50_000}, - {emitterChainID: vaa.ChainIDKarura, dailyLimit: 500_000, bigTransactionSize: 50_000}, - {emitterChainID: vaa.ChainIDAcala, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDKarura, dailyLimit: 150_000, bigTransactionSize: 15_000}, + {emitterChainID: vaa.ChainIDAcala, dailyLimit: 100_000, bigTransactionSize: 10_000}, {emitterChainID: vaa.ChainIDKlaytn, dailyLimit: 500_000, bigTransactionSize: 50_000}, {emitterChainID: vaa.ChainIDCelo, dailyLimit: 2_000_000, bigTransactionSize: 200_000}, - {emitterChainID: vaa.ChainIDNear, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDNear, dailyLimit: 150_000, bigTransactionSize: 15_000}, {emitterChainID: vaa.ChainIDMoonbeam, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, - {emitterChainID: vaa.ChainIDTerra2, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDTerra2, dailyLimit: 100_000, bigTransactionSize: 10_000}, {emitterChainID: vaa.ChainIDInjective, dailyLimit: 500_000, bigTransactionSize: 50_000}, {emitterChainID: vaa.ChainIDSui, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, {emitterChainID: vaa.ChainIDAptos, dailyLimit: 1_000_000, bigTransactionSize: 100_000}, {emitterChainID: vaa.ChainIDArbitrum, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, {emitterChainID: vaa.ChainIDOptimism, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, - {emitterChainID: vaa.ChainIDXpla, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDXpla, dailyLimit: 50_000, bigTransactionSize: 5_000}, {emitterChainID: vaa.ChainIDBase, dailyLimit: 2_000_000, bigTransactionSize: 200_000}, {emitterChainID: vaa.ChainIDSei, dailyLimit: 5_000_000, bigTransactionSize: 500_000}, {emitterChainID: vaa.ChainIDScroll, dailyLimit: 500_000, bigTransactionSize: 50_000}, - {emitterChainID: vaa.ChainIDMantle, dailyLimit: 500_000, bigTransactionSize: 50_000}, - {emitterChainID: vaa.ChainIDBlast, dailyLimit: 500_000, bigTransactionSize: 50_000}, - {emitterChainID: vaa.ChainIDXLayer, dailyLimit: 500_000, bigTransactionSize: 50_000}, + {emitterChainID: vaa.ChainIDMantle, dailyLimit: 100_000, bigTransactionSize: 10_000}, + {emitterChainID: vaa.ChainIDBlast, dailyLimit: 100_000, bigTransactionSize: 10_000}, + {emitterChainID: vaa.ChainIDXLayer, dailyLimit: 100_000, bigTransactionSize: 10_000}, {emitterChainID: vaa.ChainIDWormchain, dailyLimit: 500_000, bigTransactionSize: 50_000}, {emitterChainID: vaa.ChainIDSnaxchain, dailyLimit: 500_000, bigTransactionSize: 50_000}, } diff --git a/node/pkg/governor/mainnet_chains_test.go b/node/pkg/governor/mainnet_chains_test.go index 71e5c8aeb0..5607c8a9c8 100644 --- a/node/pkg/governor/mainnet_chains_test.go +++ b/node/pkg/governor/mainnet_chains_test.go @@ -20,8 +20,11 @@ func TestChainListSize(t *testing.T) { func TestChainDailyLimitRange(t *testing.T) { chainConfigEntries := chainList() - /* This IS a hard limit, if daily limit is set to zero it would - basically mean no value movement is allowed for that chain*/ + /* + If a chain is deprecated, we want to make sure its still governed + in the case that it is used. This will effectively stall all + transfers for 24 hours on a deprecated chain. + */ min_daily_limit := uint64(0) /* This IS NOT a hard limit, we can adjust it up as we see fit, @@ -36,7 +39,7 @@ func TestChainDailyLimitRange(t *testing.T) { /* Assuming that a governed chains should always be more than zero and less than 50,000,001 */ for _, chainConfigEntry := range chainConfigEntries { t.Run(chainConfigEntry.emitterChainID.String(), func(t *testing.T) { - assert.Greater(t, chainConfigEntry.dailyLimit, min_daily_limit) + assert.GreaterOrEqual(t, chainConfigEntry.dailyLimit, min_daily_limit) assert.Less(t, chainConfigEntry.dailyLimit, max_daily_limit) }) } @@ -62,6 +65,13 @@ func TestChainListBigTransfers(t *testing.T) { chainConfigEntries := chainList() for _, e := range chainConfigEntries { + + // If the daily limit is 0 then both the big TX and daily limit should be 0. + if e.dailyLimit == 0 { + assert.Equal(t, e.bigTransactionSize, e.dailyLimit) + continue + } + // it's always ideal to have bigTransactionSize be less than dailyLimit assert.Less(t, e.bigTransactionSize, e.dailyLimit) diff --git a/node/pkg/watchers/evm/ccq.go b/node/pkg/watchers/evm/ccq.go index 8faa5a4516..9d9e5eb5d1 100644 --- a/node/pkg/watchers/evm/ccq.go +++ b/node/pkg/watchers/evm/ccq.go @@ -143,7 +143,7 @@ func (w *Watcher) ccqHandleEthCallQueryRequest(ctx context.Context, queryRequest // Verify that the block read was successful. if err := w.ccqVerifyBlockResult(blockError, blockResult); err != nil { - w.ccqLogger.Debug("failed to verify block for eth_call query", + w.ccqLogger.Error("failed to verify block for eth_call query", zap.String("requestId", requestId), zap.String("block", block), zap.Any("batch", batch), @@ -153,19 +153,10 @@ func (w *Watcher) ccqHandleEthCallQueryRequest(ctx context.Context, queryRequest return } - w.ccqLogger.Info("query complete for eth_call", - zap.String("requestId", requestId), - zap.String("block", block), - zap.String("blockNumber", blockResult.Number.String()), - zap.String("blockHash", blockResult.Hash.Hex()), - zap.String("blockTime", blockResult.Time.String()), - zap.Int64("duration", time.Since(start).Milliseconds()), - ) - // Verify all the call results and build the batch of results. results, err := w.ccqVerifyAndExtractQueryResults(requestId, evmCallData) if err != nil { - w.ccqLogger.Debug("failed to process eth_call query call request", + w.ccqLogger.Error("failed to process eth_call query call request", zap.String("requestId", requestId), zap.String("block", block), zap.Any("batch", batch), @@ -175,6 +166,15 @@ func (w *Watcher) ccqHandleEthCallQueryRequest(ctx context.Context, queryRequest return } + w.ccqLogger.Info("query complete for eth_call", + zap.String("requestId", requestId), + zap.String("block", block), + zap.String("blockNumber", blockResult.Number.String()), + zap.String("blockHash", blockResult.Hash.Hex()), + zap.String("blockTime", blockResult.Time.String()), + zap.Int64("duration", time.Since(start).Milliseconds()), + ) + // Finally, build the response and publish it. resp := query.EthCallQueryResponse{ BlockNumber: blockResult.Number.ToInt().Uint64(), @@ -388,7 +388,7 @@ func (w *Watcher) ccqHandleEthCallByTimestampQueryRequest(ctx context.Context, q // Verify the following block read was successful. if err := w.ccqVerifyBlockResult(nextBlockError, nextBlockResult); err != nil { - w.ccqLogger.Debug("failed to verify next block for eth_call_by_timestamp query", + w.ccqLogger.Error("failed to verify next block for eth_call_by_timestamp query", zap.String("requestId", requestId), zap.String("block", block), zap.String("nextBlock", nextBlock), @@ -446,6 +446,20 @@ func (w *Watcher) ccqHandleEthCallByTimestampQueryRequest(ctx context.Context, q return } + // Verify all the call results and build the batch of results. + results, err := w.ccqVerifyAndExtractQueryResults(requestId, evmCallData) + if err != nil { + w.ccqLogger.Error("failed to process eth_call_by_timestamp query call request", + zap.String("requestId", requestId), + zap.String("block", block), + zap.String("nextBlock", nextBlock), + zap.Any("batch", batch), + zap.Error(err), + ) + w.ccqSendQueryResponse(queryRequest, query.QueryRetryNeeded, nil) + return + } + w.ccqLogger.Info("query complete for eth_call_by_timestamp", zap.String("requestId", requestId), zap.Uint64("desiredTimestamp", req.TargetTimestamp), @@ -460,20 +474,6 @@ func (w *Watcher) ccqHandleEthCallByTimestampQueryRequest(ctx context.Context, q zap.Int64("duration", time.Since(start).Milliseconds()), ) - // Verify all the call results and build the batch of results. - results, err := w.ccqVerifyAndExtractQueryResults(requestId, evmCallData) - if err != nil { - w.ccqLogger.Debug("failed to process eth_call_by_timestamp query call request", - zap.String("requestId", requestId), - zap.String("block", block), - zap.String("nextBlock", nextBlock), - zap.Any("batch", batch), - zap.Error(err), - ) - w.ccqSendQueryResponse(queryRequest, query.QueryRetryNeeded, nil) - return - } - // Finally, build the response and publish it. resp := query.EthCallByTimestampQueryResponse{ TargetBlockNumber: targetBlockNum, @@ -557,7 +557,7 @@ func (w *Watcher) ccqHandleEthCallWithFinalityQueryRequest(ctx context.Context, // Verify that the block read was successful. if err := w.ccqVerifyBlockResult(blockError, blockResult); err != nil { - w.ccqLogger.Debug("failed to verify block for eth_call_with_finality query", + w.ccqLogger.Error("failed to verify block for eth_call_with_finality query", zap.String("requestId", requestId), zap.String("block", block), zap.Any("batch", batch), @@ -590,20 +590,10 @@ func (w *Watcher) ccqHandleEthCallWithFinalityQueryRequest(ctx context.Context, return } - w.ccqLogger.Info("query complete for eth_call_with_finality", - zap.String("requestId", requestId), - zap.String("finality", req.Finality), - zap.Uint64("requestedBlockNumber", blockNumber), - zap.Uint64("latestBlockNumber", latestBlockNum), - zap.String("blockHash", blockResult.Hash.Hex()), - zap.String("blockTime", blockResult.Time.String()), - zap.Int64("duration", time.Since(start).Milliseconds()), - ) - // Verify all the call results and build the batch of results. results, err := w.ccqVerifyAndExtractQueryResults(requestId, evmCallData) if err != nil { - w.ccqLogger.Debug("failed to process eth_call_with_finality query call request", + w.ccqLogger.Error("failed to process eth_call_with_finality query call request", zap.String("requestId", requestId), zap.String("finality", req.Finality), zap.Uint64("requestedBlockNumber", blockNumber), @@ -616,6 +606,16 @@ func (w *Watcher) ccqHandleEthCallWithFinalityQueryRequest(ctx context.Context, return } + w.ccqLogger.Info("query complete for eth_call_with_finality", + zap.String("requestId", requestId), + zap.String("finality", req.Finality), + zap.Uint64("requestedBlockNumber", blockNumber), + zap.Uint64("latestBlockNumber", latestBlockNum), + zap.String("blockHash", blockResult.Hash.Hex()), + zap.String("blockTime", blockResult.Time.String()), + zap.Int64("duration", time.Since(start).Milliseconds()), + ) + // Finally, build the response and publish it. resp := query.EthCallWithFinalityQueryResponse{ BlockNumber: blockNumber, diff --git a/node/pkg/watchers/evm/connectors/batch_poller.go b/node/pkg/watchers/evm/connectors/batch_poller.go index adabc05d26..1262350cac 100644 --- a/node/pkg/watchers/evm/connectors/batch_poller.go +++ b/node/pkg/watchers/evm/connectors/batch_poller.go @@ -82,6 +82,17 @@ func (b *BatchPollConnector) SubscribeForBlocks(ctx context.Context, errC chan e errCount := 0 + // Publish the initial finalized and safe blocks so we have a starting point for reobservation requests. + for idx, block := range lastBlocks { + b.logger.Info(fmt.Sprintf("publishing initial %s block", b.batchData[idx].finality), zap.Uint64("initial_block", block.Number.Uint64())) + sink <- block + if b.generateSafe && b.batchData[idx].finality == Finalized { + safe := block.Copy(Safe) + b.logger.Info("publishing generated initial safe block", zap.Uint64("initial_block", safe.Number.Uint64())) + sink <- safe + } + } + common.RunWithScissors(ctx, errC, "block_poll_subscribe_for_blocks", func(ctx context.Context) error { timer := time.NewTimer(b.Delay) defer timer.Stop() diff --git a/node/pkg/watchers/evm/connectors/batch_poller_test.go b/node/pkg/watchers/evm/connectors/batch_poller_test.go index 8f39495fd3..8cf7052290 100644 --- a/node/pkg/watchers/evm/connectors/batch_poller_test.go +++ b/node/pkg/watchers/evm/connectors/batch_poller_test.go @@ -281,12 +281,13 @@ func TestBatchPoller(t *testing.T) { } }() - // First sleep a bit and make sure there were no start up errors and no blocks got published. + // First sleep a bit and make sure there were no start up errors and the initial blocks were published. time.Sleep(10 * time.Millisecond) mutex.Lock() require.NoError(t, publishedErr) require.NoError(t, publishedSubErr) - assert.Nil(t, block) + batchShouldHaveSafeAndFinalizedButNotLatest(t, block, 0x309a0c, baseConnector.expectedHash()) + block = nil mutex.Unlock() // Post the first new block and verify we get it.