diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index 2c3fc20235..ecb0a9adae 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -611,6 +611,7 @@ dependencies = [ name = "cw20-wrapped-2" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw2 0.13.4", @@ -1244,6 +1245,7 @@ dependencies = [ name = "mock-bridge-integration-2" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cosmwasm-vm", @@ -1976,6 +1978,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" name = "token-bridge-cosmwasm" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw20", @@ -2624,6 +2627,7 @@ dependencies = [ name = "wormhole-cosmwasm" version = "0.1.0" dependencies = [ + "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "generic-array", diff --git a/cosmwasm/contracts/cw20-wrapped/Cargo.toml b/cosmwasm/contracts/cw20-wrapped/Cargo.toml index e7dcf4a51d..13ec0b6321 100644 --- a/cosmwasm/contracts/cw20-wrapped/Cargo.toml +++ b/cosmwasm/contracts/cw20-wrapped/Cargo.toml @@ -16,6 +16,7 @@ library = [] [dependencies] cosmwasm-std = { version = "1.0.0" } cosmwasm-storage = { version = "1.0.0" } +cosmwasm-schema = { version = "1.0.0" } schemars = "0.8.8" serde = { version = "1.0.137", default-features = false, features = ["derive"] } cw2 = { version = "0.13.2" } diff --git a/cosmwasm/contracts/cw20-wrapped/src/examples/cw20_wrapped_schema.rs b/cosmwasm/contracts/cw20-wrapped/src/examples/cw20_wrapped_schema.rs new file mode 100644 index 0000000000..799b182c87 --- /dev/null +++ b/cosmwasm/contracts/cw20-wrapped/src/examples/cw20_wrapped_schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use cw20_wrapped_2::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} \ No newline at end of file diff --git a/cosmwasm/contracts/cw20-wrapped/src/msg.rs b/cosmwasm/contracts/cw20-wrapped/src/msg.rs index 2c82c4e4c9..c68abcd017 100644 --- a/cosmwasm/contracts/cw20-wrapped/src/msg.rs +++ b/cosmwasm/contracts/cw20-wrapped/src/msg.rs @@ -1,13 +1,12 @@ #![allow(clippy::field_reassign_with_default)] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, Uint128}; -use cw20::Expiration; +use cw20::{AllowanceResponse, BalanceResponse, Expiration, TokenInfoResponse}; type HumanAddr = String; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct InstantiateMsg { pub name: String, pub symbol: String, @@ -18,24 +17,22 @@ pub struct InstantiateMsg { pub init_hook: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct InitHook { pub msg: Binary, pub contract_addr: HumanAddr, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct InitMint { pub recipient: HumanAddr, pub amount: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { /// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions Transfer { @@ -94,17 +91,24 @@ pub enum ExecuteMsg { UpdateMetadata { name: String, symbol: String }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { - // Generic information about the wrapped asset + #[returns(WrappedAssetInfoResponse)] + /// Generic information about the wrapped asset WrappedAssetInfo {}, + + #[returns(BalanceResponse)] /// Implements CW20. Returns the current balance of the given address, 0 if unset. Balance { address: HumanAddr, }, + + #[returns(TokenInfoResponse)] /// Implements CW20. Returns metadata on the contract - name, decimals, supply, etc. TokenInfo {}, + + #[returns(AllowanceResponse)] /// Implements CW20 "allowance" extension. /// Returns how much spender can use from owner account, 0 if unset. Allowance { @@ -113,7 +117,7 @@ pub enum QueryMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct WrappedAssetInfoResponse { pub asset_chain: u16, // Asset chain id pub asset_address: Binary, // Asset smart contract address in the original chain diff --git a/cosmwasm/contracts/ibc-translator/src/examples/ibc_translator_schema.rs b/cosmwasm/contracts/ibc-translator/src/examples/ibc_translator_schema.rs new file mode 100644 index 0000000000..ea88b77f6c --- /dev/null +++ b/cosmwasm/contracts/ibc-translator/src/examples/ibc_translator_schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use ibc_translator::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} \ No newline at end of file diff --git a/cosmwasm/contracts/mock-bridge-integration/Cargo.toml b/cosmwasm/contracts/mock-bridge-integration/Cargo.toml index 1c8226c77e..d5ec88fcd3 100644 --- a/cosmwasm/contracts/mock-bridge-integration/Cargo.toml +++ b/cosmwasm/contracts/mock-bridge-integration/Cargo.toml @@ -15,6 +15,7 @@ library = [] [dependencies] cosmwasm-std = { version = "1.0.0" } cosmwasm-storage = { version = "1.0.0" } +cosmwasm-schema = { version = "1.0.0" } schemars = "0.8.8" serde = { version = "1.0.137", default-features = false, features = ["derive"] } diff --git a/cosmwasm/contracts/mock-bridge-integration/src/msg.rs b/cosmwasm/contracts/mock-bridge-integration/src/msg.rs index 8970a8f1fd..b86acc6794 100644 --- a/cosmwasm/contracts/mock-bridge-integration/src/msg.rs +++ b/cosmwasm/contracts/mock-bridge-integration/src/msg.rs @@ -1,26 +1,24 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Binary; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; type HumanAddr = String; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct InstantiateMsg { pub token_bridge_contract: HumanAddr, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { CompleteTransferWithPayload { data: Binary }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { + #[returns(())] WrappedRegistry { chain: u16, address: Binary }, } diff --git a/cosmwasm/contracts/token-bridge/Cargo.toml b/cosmwasm/contracts/token-bridge/Cargo.toml index 67c72f44d8..05b9275ea6 100644 --- a/cosmwasm/contracts/token-bridge/Cargo.toml +++ b/cosmwasm/contracts/token-bridge/Cargo.toml @@ -21,6 +21,7 @@ default = ["full"] [dependencies] cosmwasm-std = { version = "1.0.0" } cosmwasm-storage = { version = "1.0.0" } +cosmwasm-schema = { version = "1.0.0" } schemars = "0.8.8" serde = { version = "1.0.137", default-features = false, features = ["derive"] } cw20 = "0.13.2" diff --git a/cosmwasm/contracts/token-bridge/src/msg.rs b/cosmwasm/contracts/token-bridge/src/msg.rs index e0a78bdf9a..1bc28b5e5a 100644 --- a/cosmwasm/contracts/token-bridge/src/msg.rs +++ b/cosmwasm/contracts/token-bridge/src/msg.rs @@ -1,6 +1,5 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Binary, Uint128}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use crate::token_address::{ExternalTokenId, TokenId}; @@ -8,7 +7,7 @@ type HumanAddr = String; /// The instantiation parameters of the token bridge contract. See /// [`crate::state::ConfigInfo`] for more details on what these fields mean. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct InstantiateMsg { pub gov_chain: u16, pub gov_address: Binary, @@ -22,8 +21,7 @@ pub struct InstantiateMsg { pub native_decimals: u8, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { RegisterAssetHook { chain: u16, @@ -67,28 +65,30 @@ pub enum ExecuteMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { + #[returns(WrappedRegistryResponse)] WrappedRegistry { chain: u16, address: Binary }, + #[returns(TransferInfoResponse)] TransferInfo { vaa: Binary }, + #[returns(ExternalIdResponse)] ExternalId { external_id: Binary }, + #[returns(IsVaaRedeemedResponse)] IsVaaRedeemed { vaa: Binary }, + #[returns(ChainRegistrationResponse)] ChainRegistration { chain: u16 }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct WrappedRegistryResponse { pub address: HumanAddr, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct TransferInfoResponse { pub amount: Uint128, pub token_address: [u8; 32], @@ -99,26 +99,22 @@ pub struct TransferInfoResponse { pub payload: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ExternalIdResponse { pub token_id: TokenId, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct IsVaaRedeemedResponse { pub is_redeemed: bool, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct ChainRegistrationResponse { pub address: Binary, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct CompleteTransferResponse { // All addresses are bech32-encoded strings. @@ -132,7 +128,7 @@ pub struct CompleteTransferResponse { pub fee: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[cw_serde] pub struct Asset { pub info: AssetInfo, pub amount: Uint128, @@ -140,8 +136,7 @@ pub struct Asset { /// AssetInfo contract_addr is usually passed from the cw20 hook /// so we can trust the contract_addr is properly validated. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum AssetInfo { Token { contract_addr: String }, NativeToken { denom: String }, diff --git a/cosmwasm/contracts/wormchain-ibc-receiver/src/examples/wormchain_ibc_receiver_schema.rs b/cosmwasm/contracts/wormchain-ibc-receiver/src/examples/wormchain_ibc_receiver_schema.rs new file mode 100644 index 0000000000..5547fe0c24 --- /dev/null +++ b/cosmwasm/contracts/wormchain-ibc-receiver/src/examples/wormchain_ibc_receiver_schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use cosmwasm_std::Empty; +use wormchain_ibc_receiver::msg::{ExecuteMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: Empty, + execute: ExecuteMsg, + query: QueryMsg, + } +} \ No newline at end of file diff --git a/cosmwasm/contracts/wormhole-ibc/src/examples/wormhole_ibc_schema.rs b/cosmwasm/contracts/wormhole-ibc/src/examples/wormhole_ibc_schema.rs new file mode 100644 index 0000000000..07965c550b --- /dev/null +++ b/cosmwasm/contracts/wormhole-ibc/src/examples/wormhole_ibc_schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use cw_wormhole::msg::{InstantiateMsg, QueryMsg}; +use wormhole_ibc::msg::ExecuteMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} \ No newline at end of file diff --git a/cosmwasm/contracts/wormhole/Cargo.toml b/cosmwasm/contracts/wormhole/Cargo.toml index 5c1dc2405a..2d5fdd9b96 100644 --- a/cosmwasm/contracts/wormhole/Cargo.toml +++ b/cosmwasm/contracts/wormhole/Cargo.toml @@ -21,6 +21,7 @@ default = ["full"] [dependencies] cosmwasm-std = { version = "1.0.0" } cosmwasm-storage = { version = "1.0.0" } +cosmwasm-schema = { version = "1.0.0" } schemars = "0.8.8" serde = { version = "1.0.137", default-features = false, features = ["derive"] } thiserror = { version = "1.0.31" } diff --git a/cosmwasm/contracts/wormhole/src/examples/wormhole_schema.rs b/cosmwasm/contracts/wormhole/src/examples/wormhole_schema.rs new file mode 100644 index 0000000000..c3a87e0017 --- /dev/null +++ b/cosmwasm/contracts/wormhole/src/examples/wormhole_schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use cw_wormhole::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} \ No newline at end of file diff --git a/cosmwasm/contracts/wormhole/src/msg.rs b/cosmwasm/contracts/wormhole/src/msg.rs index 8f9941cd9c..e0f770eaf0 100644 --- a/cosmwasm/contracts/wormhole/src/msg.rs +++ b/cosmwasm/contracts/wormhole/src/msg.rs @@ -1,14 +1,13 @@ +use cosmwasm_schema::{ cw_serde, QueryResponses}; use cosmwasm_std::{Binary, Coin}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use crate::state::{GuardianAddress, GuardianSetInfo}; +use crate::state::{GuardianAddress, GuardianSetInfo, ParsedVAA}; type HumanAddr = String; /// The instantiation parameters of the core bridge contract. See /// [`crate::state::ConfigInfo`] for more details on what these fields mean. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct InstantiateMsg { pub gov_chain: u16, pub gov_address: Binary, @@ -21,47 +20,45 @@ pub struct InstantiateMsg { pub fee_denom: String, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum ExecuteMsg { SubmitVAA { vaa: Binary }, PostMessage { message: Binary, nonce: u32 }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct MigrateMsg {} -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] +#[derive(QueryResponses)] pub enum QueryMsg { + #[returns(GuardianSetInfoResponse)] GuardianSetInfo {}, + #[returns(ParsedVAA)] VerifyVAA { vaa: Binary, block_time: u64 }, + #[returns(GetStateResponse)] GetState {}, + #[returns(GetAddressHexResponse)] QueryAddressHex { address: HumanAddr }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct GuardianSetInfoResponse { pub guardian_set_index: u32, // Current guardian set index pub addresses: Vec, // List of querdian addresses } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct WrappedRegistryResponse { pub address: HumanAddr, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct GetStateResponse { pub fee: Coin, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct GetAddressHexResponse { pub hex: String, } diff --git a/node/pkg/db/db_test.go b/node/pkg/db/db_test.go index e58f7dfc04..543d1b6cd6 100644 --- a/node/pkg/db/db_test.go +++ b/node/pkg/db/db_test.go @@ -1,130 +1,290 @@ -package processor +package db import ( - "encoding/hex" - "time" - - "github.com/mr-tron/base58" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" + "bytes" + "crypto/ecdsa" + "crypto/rand" + "fmt" + math_rand "math/rand" + "os" + "runtime" + "sync" + "sync/atomic" - ethCommon "github.com/ethereum/go-ethereum/common" + "github.com/dgraph-io/badger/v3" "github.com/ethereum/go-ethereum/crypto" + "github.com/wormhole-foundation/wormhole/sdk/vaa" "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "github.com/certusone/wormhole/node/pkg/common" - "github.com/wormhole-foundation/wormhole/sdk/vaa" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var ( - // SECURITY: source_chain/target_chain are untrusted uint8 values. An attacker could cause a maximum of 255**2 label - // pairs to be created, which is acceptable. +func getVAA() vaa.VAA { + return getVAAWithSeqNum(1) +} - messagesObservedTotal = promauto.NewCounterVec( - prometheus.CounterOpts{ - Name: "wormhole_message_observations_total", - Help: "Total number of messages observed", - }, - []string{"emitter_chain"}) -) +func getVAAWithSeqNum(seqNum uint64) vaa.VAA { + var payload = []byte{97, 97, 97, 97, 97, 97} + var governanceEmitter = vaa.Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4} + + return vaa.VAA{ + Version: uint8(1), + GuardianSetIndex: uint32(1), + Signatures: nil, + Timestamp: time.Unix(0, 0), + Nonce: uint32(1), + Sequence: seqNum, + ConsistencyLevel: uint8(32), + EmitterChain: vaa.ChainIDSolana, + EmitterAddress: governanceEmitter, + Payload: payload, + } +} + +// Testing the expected default behavior of a CreateGovernanceVAA +func TestVaaIDFromString(t *testing.T) { + vaaIdString := "1/0000000000000000000000000000000000000000000000000000000000000004/1" + vaaID, _ := VaaIDFromString(vaaIdString) + expectAddr := vaa.Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4} + + assert.Equal(t, vaa.ChainIDSolana, vaaID.EmitterChain) + assert.Equal(t, expectAddr, vaaID.EmitterAddress) + assert.Equal(t, uint64(1), vaaID.Sequence) +} + +func TestVaaIDFromVAA(t *testing.T) { + testVaa := getVAA() + vaaID := VaaIDFromVAA(&testVaa) + expectAddr := vaa.Address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4} + + assert.Equal(t, vaa.ChainIDSolana, vaaID.EmitterChain) + assert.Equal(t, expectAddr, vaaID.EmitterAddress) + assert.Equal(t, uint64(1), vaaID.Sequence) +} + +func TestBytes(t *testing.T) { + vaaIdString := "1/0000000000000000000000000000000000000000000000000000000000000004/1" + vaaID, _ := VaaIDFromString(vaaIdString) + expected := []byte{0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2f, 0x31, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x2f, 0x31} + + assert.Equal(t, expected, vaaID.Bytes()) +} + +func TestEmitterPrefixBytesWithChainIDAndAddress(t *testing.T) { + vaaIdString := "1/0000000000000000000000000000000000000000000000000000000000000004/1" + vaaID, _ := VaaIDFromString(vaaIdString) + expected := []byte{0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2f, 0x31, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34} + + assert.Equal(t, expected, vaaID.EmitterPrefixBytes()) +} + +func TestEmitterPrefixBytesWithOnlyChainID(t *testing.T) { + vaaID := VAAID{EmitterChain: vaa.ChainID(26)} + assert.Equal(t, []byte("signed/26"), vaaID.EmitterPrefixBytes()) +} + +func TestStoreSignedVAAUnsigned(t *testing.T) { + dbPath := t.TempDir() + db := OpenDb(zap.NewNop(), &dbPath) + defer db.Close() + defer os.Remove(dbPath) + + testVaa := getVAA() + + // Should panic because the VAA is not signed + assert.Panics(t, func() { db.StoreSignedVAA(&testVaa) }, "The code did not panic") //nolint:errcheck +} + +func TestStoreSignedVAASigned(t *testing.T) { + dbPath := t.TempDir() + db := OpenDb(zap.NewNop(), &dbPath) + defer db.Close() + defer os.Remove(dbPath) + + testVaa := getVAA() + + privKey, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + testVaa.AddSignature(privKey, 0) + + err2 := db.StoreSignedVAA(&testVaa) + assert.NoError(t, err2) +} + +func TestStoreSignedVAABatch(t *testing.T) { + dbPath := t.TempDir() + db := OpenDb(zap.NewNop(), &dbPath) + defer db.Close() + defer os.Remove(dbPath) -// handleMessage processes a message received from a chain and instantiates our deterministic copy of the VAA. An -// event may be received multiple times and must be handled in an idempotent fashion. -func (p *Processor) handleMessage(k *common.MessagePublication) { - if p.gs == nil { - p.logger.Warn("dropping observation since we haven't initialized our guardian set yet", - zap.String("message_id", k.MessageIDString()), - zap.Uint32("nonce", k.Nonce), - zap.Stringer("txhash", k.TxHash), - zap.Time("timestamp", k.Timestamp), - ) - return + privKey, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + require.NoError(t, err) + + require.Less(t, int64(0), db.db.MaxBatchCount()) // In testing this was 104857. + require.Less(t, int64(0), db.db.MaxBatchSize()) // In testing this was 10066329. + + // Make sure we exceed the max batch size. + numVAAs := uint64(db.db.MaxBatchCount() + 1) + + // Build the VAA batch. + vaaBatch := make([]*vaa.VAA, 0, numVAAs) + for seqNum := uint64(0); seqNum < numVAAs; seqNum++ { + v := getVAAWithSeqNum(seqNum) + v.AddSignature(privKey, 0) + vaaBatch = append(vaaBatch, &v) } - messagesObservedTotal.WithLabelValues(k.EmitterChain.String()).Inc() - - // All nodes will create the exact same VAA and sign its digest. - // Consensus is established on this digest. - - v := &VAA{ - VAA: vaa.VAA{ - Version: vaa.SupportedVAAVersion, - GuardianSetIndex: p.gs.Index, - Signatures: nil, - Timestamp: k.Timestamp, - Nonce: k.Nonce, - EmitterChain: k.EmitterChain, - EmitterAddress: k.EmitterAddress, - Payload: k.Payload, - Sequence: k.Sequence, - ConsistencyLevel: k.ConsistencyLevel, - }, - Unreliable: k.Unreliable, - Reobservation: k.IsReobservation, + // Store the batch in the database. + err = db.StoreSignedVAABatch(vaaBatch) + require.NoError(t, err) + + // Verify all the VAAs are in the database. + for _, v := range vaaBatch { + storedBytes, err := db.GetSignedVAABytes(*VaaIDFromVAA(v)) + require.NoError(t, err) + + origBytes, err := v.Marshal() + require.NoError(t, err) + + assert.True(t, bytes.Equal(origBytes, storedBytes)) + } + + // Verify that updates work as well by tweaking the VAAs and rewriting them. + for _, v := range vaaBatch { + v.Nonce += 1 } - // Generate digest of the unsigned VAA. - digest := v.SigningDigest() - hash := hex.EncodeToString(digest.Bytes()) + // Store the updated batch in the database. + err = db.StoreSignedVAABatch(vaaBatch) + require.NoError(t, err) - // Sign the digest using our node's guardian key. - signature, err := crypto.Sign(digest.Bytes(), p.gk) - if err != nil { - panic(err) + // Verify all the updated VAAs are in the database. + for _, v := range vaaBatch { + storedBytes, err := db.GetSignedVAABytes(*VaaIDFromVAA(v)) + require.NoError(t, err) + + origBytes, err := v.Marshal() + require.NoError(t, err) + + assert.True(t, bytes.Equal(origBytes, storedBytes)) + } +} + +func TestGetSignedVAABytes(t *testing.T) { + dbPath := t.TempDir() + db := OpenDb(zap.NewNop(), &dbPath) + defer db.Close() + defer os.Remove(dbPath) + + testVaa := getVAA() + + vaaID := VaaIDFromVAA(&testVaa) + + privKey, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + testVaa.AddSignature(privKey, 0) + + // Store full VAA + err2 := db.StoreSignedVAA(&testVaa) + assert.NoError(t, err2) + + // Retrieve it using vaaID + vaaBytes, err2 := db.GetSignedVAABytes(*vaaID) + assert.NoError(t, err2) + + testVaaBytes, err3 := testVaa.Marshal() + assert.NoError(t, err3) + + assert.Equal(t, testVaaBytes, vaaBytes) +} + +func TestFindEmitterSequenceGap(t *testing.T) { + dbPath := t.TempDir() + db := OpenDb(zap.NewNop(), &dbPath) + defer db.Close() + defer os.Remove(dbPath) + + testVaa := getVAA() + + vaaID := VaaIDFromVAA(&testVaa) + + privKey, _ := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + testVaa.AddSignature(privKey, 0) + + // Store full VAA + err2 := db.StoreSignedVAA(&testVaa) + assert.NoError(t, err2) + + resp, firstSeq, lastSeq, err := db.FindEmitterSequenceGap(*vaaID) + + assert.Equal(t, []uint64{0x0}, resp) + assert.Equal(t, uint64(0x0), firstSeq) + assert.Equal(t, uint64(0x1), lastSeq) + assert.NoError(t, err) +} + +// BenchmarkVaaLookup benchmarks db.GetSignedVAABytes +// You need to set the environment variable WH_DBPATH to a path with a populated BadgerDB. +// You may want to play with the CONCURRENCY parameter. +func BenchmarkVaaLookup(b *testing.B) { + CONCURRENCY := runtime.NumCPU() + dbPath := os.Getenv("WH_DBPATH") + require.NotEqual(b, dbPath, "") + + // open DB + optionsDB := badger.DefaultOptions(dbPath) + optionsDB.Logger = nil + badgerDb, err := badger.Open(optionsDB) + require.NoError(b, err) + db := &Database{ + db: badgerDb, } - shouldPublishImmediately := p.shouldPublishImmediately(&v.VAA) - - if p.logger.Core().Enabled(zapcore.DebugLevel) { - p.logger.Debug("observed and signed confirmed message publication", - zap.String("message_id", k.MessageIDString()), - zap.Stringer("txhash", k.TxHash), - zap.String("txhash_b58", base58.Encode(k.TxHash.Bytes())), - zap.String("hash", hash), - zap.Uint32("nonce", k.Nonce), - zap.Time("timestamp", k.Timestamp), - zap.Uint8("consistency_level", k.ConsistencyLevel), - zap.String("signature", hex.EncodeToString(signature)), - zap.Bool("shouldPublishImmediately", shouldPublishImmediately), - zap.Bool("isReobservation", k.IsReobservation), - ) + if err != nil { + b.Error("failed to open database") } + defer db.Close() + + vaaIds := make(chan *VAAID, b.N) - // Broadcast the signature. - ourObs, msg := p.broadcastSignature(v.MessageID(), k.TxHash.Bytes(), digest, signature, shouldPublishImmediately) + for i := 0; i < b.N; i++ { + randId := math_rand.Intn(250000) //nolint + randId = 250000 - (i / 18) + vaaId, err := VaaIDFromString(fmt.Sprintf("4/000000000000000000000000b6f6d86a8f9879a9c87f643768d9efc38c1da6e7/%d", randId)) + assert.NoError(b, err) + vaaIds <- vaaId + } - // Indicate that we observed this one. - observationsReceivedTotal.Inc() - observationsReceivedByGuardianAddressTotal.WithLabelValues(p.ourAddr.Hex()).Inc() + b.ResetTimer() - // Get / create our state entry. - s := p.state.signatures[hash] - if s == nil { - s = &state{ - firstObserved: time.Now(), - nextRetry: time.Now().Add(nextRetryDuration(0)), - signatures: map[ethCommon.Address][]byte{}, - source: "loopback", - } + // actual timed code + var errCtr atomic.Int32 + var wg sync.WaitGroup - p.state.signatures[hash] = s + for i := 0; i < CONCURRENCY; i++ { + wg.Add(1) + go func() { + for { + select { + case vaaId := <-vaaIds: + _, err = db.GetSignedVAABytes(*vaaId) + if err != nil { + fmt.Printf("error retrieving %s/%s/%d: %s\n", vaaId.EmitterChain, vaaId.EmitterAddress, vaaId.Sequence, err) + errCtr.Add(1) + } + default: + wg.Done() + return + } + } + }() } - // Update our state. - s.ourObservation = v - s.txHash = k.TxHash.Bytes() - s.source = v.GetEmitterChain().String() - s.gs = p.gs // guaranteed to match ourObservation - there's no concurrent access to p.gs - s.signatures[p.ourAddr] = signature - s.ourObs = ourObs - s.ourMsg = msg - - // Fast path for our own signature. - if !s.submitted { - start := time.Now() - p.checkForQuorum(ourObs, s, s.gs, hash) - timeToHandleObservation.Observe(float64(time.Since(start).Microseconds())) + wg.Wait() + + if int(errCtr.Load()) > b.N/3 { + b.Error("More than 1/3 of GetSignedVAABytes failed.") } } diff --git a/wormchain/docs/.adr-dir b/wormchain/docs/.adr-dir deleted file mode 100644 index c65fbdbbea..0000000000 --- a/wormchain/docs/.adr-dir +++ /dev/null @@ -1 +0,0 @@ -architecture diff --git a/wormchain/docs/architecture/0001-ADRs-are-canonical-source-of-truth.md b/wormchain/docs/architecture/0001-ADRs-are-canonical-source-of-truth.md deleted file mode 100644 index 982d2c4154..0000000000 --- a/wormchain/docs/architecture/0001-ADRs-are-canonical-source-of-truth.md +++ /dev/null @@ -1,32 +0,0 @@ -# 1. ADRs will be the Canonical Source of Truth for architecture decisions - -Date: 2024-06-24 - -## Status - -Accepted - -## Context - -- As WH/SL -- We want a place to memorialize decisions -- Because it helps with context / institutional memory / onboarding - -As discussed and agreed to in the Strangelove / Wormhole [project kick-off](https://miro.com/app/board/uXjVK_fZYq0=/?share_link_id=596301298163). - -## Decision - -To memorialize decisions, we'll use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). - -Briefly: ~if~ whenever we make a decision which might reasonably cause a Future Developer (e.g., a new dev, or Six-Months-In-The-Future-Us) to say "Wait—why'd we choose that?", we'll log an ADR to act as a Canonical Source Of Truth, contemporaneously detailing reasoning. - -At times, we'll certainly be wrong. - -We'll almost certainly backtrack certain ideas. - -But—hopefully—we won't bark up the same tree twice. - -## Consequences - -- You might want some tooling. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools), or `brew install adr-tools` if you're the trusting sort. -- We instantiated w/ the default template (Status, Context, Decision, Consequences). If/when we want to update the ADR format, this gh issue has the rundown: https://github.com/npryce/adr-tools/issues/120. diff --git a/wormchain/docs/architecture/0002-wasmvm-will-be-upgraded-to-v1-5-2-and-not-v2-x-x.md b/wormchain/docs/architecture/0002-wasmvm-will-be-upgraded-to-v1-5-2-and-not-v2-x-x.md deleted file mode 100644 index 48a80abd45..0000000000 --- a/wormchain/docs/architecture/0002-wasmvm-will-be-upgraded-to-v1-5-2-and-not-v2-x-x.md +++ /dev/null @@ -1,22 +0,0 @@ -# 2. wasmvm will be upgraded to v1.5.2 and not v2.x.x - -Date: 2024-06-24 - -## Status - -Accepted - -## Context - -wasmvm 1.5.2 has an EOL that will arrive sooner, but v2.x.x is a riskier upgrade path. - -## Decision - -We will upgrade to v1.5.2 and NOT v2.x.x because it was suggested that the risk/reward isn't worth it: too new, -bugs on tokenfactory for chains which deployed it. We will hold off on that for now. - -## Consequences - -Not being on wasmvm v2.x.x will leave wormhole gateway missing out on the "factor of 1000" lower cosmwasm gas costs, -improved submessage ergonomics and ability to query cosmwasm via grpc which are some of the main 2.x benefits. -Reference: https://medium.com/cosmwasm/cosmwasm-2-0-bbb94126ce6f diff --git a/wormchain/docs/architecture/0003-cosmos-sdk-v0-47-upgrade-will-be-implemented-in-wormhole-fork.md b/wormchain/docs/architecture/0003-cosmos-sdk-v0-47-upgrade-will-be-implemented-in-wormhole-fork.md deleted file mode 100644 index f9ad11a592..0000000000 --- a/wormchain/docs/architecture/0003-cosmos-sdk-v0-47-upgrade-will-be-implemented-in-wormhole-fork.md +++ /dev/null @@ -1,28 +0,0 @@ -# 3. Cosmos SDK v0.47 upgrade will be implemented in Wormhole Fork - -Date: 2024-07-10 - -## Status - -Accepted - -## Context - -The Wormhole Foundation has made a copy of the Cosmos SDK repository, which can be found in their Github organization [here](https://github.com/wormhole-foundation/cosmos-sdk). They are referencing this copied repository in the wormchain [go.mod](https://github.com/wormhole-foundation/wormhole/blob/6236a9a6cbd0dc00a940e6654c6f6106d0904ece/wormchain/go.mod#L142) file, referencing the [v0.45.9-wormhole-2](https://github.com/wormhole-foundation/cosmos-sdk/releases/tag/v0.45.9-wormhole-2) tag. This tag has [commits](https://github.com/wormhole-foundation/cosmos-sdk/commits/v0.45.9-wormhole-2/?since=2022-10-19&until=2022-12-21) made by the wormhole-foundation team that fundamentally change the behavior of the staking module, particularly implementing proof of authority based on the wormchain guardian set. - -## Decision - -With the use of a forked Cosmos SDK and fundamental changes to the staking module, the initial Cosmos SDK v0.45 to v0.47 upgrade will be done on their fork in the following manner: - -1. Pull in the [v0.47.12](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.12) (latest in the v0.47 series) tag from the Cosmos SDK repository into the -2. Re-implement the changes made in the v0.45.9-wormhole-2 tag into the v0.47.12 tag. -3. Release a new tag that includes the changes from the v0.47.12 tag and the changes made on top of it from the v0.45.9-wormhole-2 tag. -4. Reference this new version in the wormchain go.mod file. - -## Consequences - -With the changes to the staking module being applied directly in the Wormhole Foundation Cosmos SDK fork, this will have the following good and bad consequences: - -1. It will maintain the required changes made to the staking module as they were at the time of the v0.45.9-wormhole-2 tag. -2. It will be easier to maintain the changes made to the staking module as they will be directly applied to the forked repository. -3. Maintaining the fork of the Cosmos SDK will be more difficult as the Cosmos SDK repository will continue to evolve. diff --git a/wormchain/docs/architecture/0004-tendermint-core-will-be-migrated-to-cometbft-by-bumping-to-latest-cosmos-sdk-upstream-v0-45.md b/wormchain/docs/architecture/0004-tendermint-core-will-be-migrated-to-cometbft-by-bumping-to-latest-cosmos-sdk-upstream-v0-45.md deleted file mode 100644 index 7f7f432665..0000000000 --- a/wormchain/docs/architecture/0004-tendermint-core-will-be-migrated-to-cometbft-by-bumping-to-latest-cosmos-sdk-upstream-v0-45.md +++ /dev/null @@ -1,33 +0,0 @@ -# 4. Tendermint Core Will be Migrated to CometBFT by Bumping to Latest Cosmos SDK Upstream v0.45 - -Date: 2024-07-12 - -## Status - -Accepted - -## Context - -The Wormhole Cosmos SDK was forked at [v0.45.9](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.9) of the Cosmos SDK releases. This can be seen by looking at their [in-use tag's](https://github.com/wormhole-foundation/cosmos-sdk/commits/v0.45.9-wormhole-2/) last commit from the upstream [here](https://github.com/wormhole-foundation/cosmos-sdk/commit/2582f0aab7b2cbf66ade066fe570a4622cf0b098), which shares the commit history and SHA of the v0.45.9 release. - -This version of the Cosmos SDK was released before the fork of Tendermint Core and migration provided by the Cosmos SDK core team in the [Comet BFT](https://github.com/cometbft/cometbft) project, the motivation of which was announced [here](https://informal.systems/blog/cosmos-meet-cometbft). - -To facilitate a more modern usage of the Cosmos SDK, projects should move away from Tendermint Core to CometBFT, as it is more up-to-date, maintained and provides security and bug fixes. - -The Cosmos SDK team slowly rolled out migrations from Tendermint Core to CometBFT in the Comsos SDK repo, and this migration was implemented in the v0.45 line in release [v0.45.15](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.15). - -## Decision - -The migration to Tendermint Core will take place by: - -1. Pulling in the upstream latest version in the v0.45 line, which is [v0.45.16](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.45.16) (one version after the v0.45.15 migration release) -2. Replaying the [commits](https://github.com/wormhole-foundation/cosmos-sdk/commits/v0.45.9-wormhole-2/?since=2022-10-19&until=2022-12-21) implemented by the Wormhole developers on top of this tag -3. Release a new tag that includes the changes from the v0.45.16 tag and the changes made on top of it from the v0.45.9-wormhole-2 tag. -4. Reference this new version in the wormchain go.mod file. - -## Consequences - -This change will have the following consequences: - -1. It pulls in the latest bug fixes and security updates in the v0.45 line while work on moving to v0.47 continues (see ADR 3 for details) -2. It will require extensive testing and review to ensure the changes from v0.45.9 to v0.45.16 did not break the Wormchain repo's usage of the Cosmos SDK