Skip to content

Commit

Permalink
feat(cw-xc): faster cosmos devnet with fix for interpreter instantiat…
Browse files Browse the repository at this point in the history
…ion (#4041)

So I tuned some bash configs of XCVM, improve startup time of cosmos net
for dev, and continue to run transfer.
Transfer failed because of
CosmWasm/cosmwasm#1810 . So fix was to make
short name for interpreter. Hash may fail too, so used counter id.

I think in next iter should use network prefixed counter. So that can
have global interpreter ids, but no must.

Tested by running cosmos devnet oneliner to setup xcvm. And forced
create interpreter for user.

Also I split XCVM admin key and other admin key, so these processes can
do tx in parallel, as it visible in devnet init graph.

Also started doing some logging for sanity
```
{"level":"info","process":"centauri","replica":1,"message":"{\"force_instantiate\":{\"user_origin\":\"centauri1e0nh3davd6d3yh73sldwndww7zchetwsae8m9rz9x6hlg7w4ysrqhmaqju\"}}"}
{"level":"info","process":"centauri","replica":1,"message":"xcvm:: {\"id\":0,\"result\":{\"ok\":{\"events\":[{\"type\":\"instantiate\",\"attributes\":[{\"key\":\"_contract_address\",\"value\":\"centauri1tlcw3llaxzs3jam7yheqxhr3k0n4y5sfrnah72n6aqt0rlp4xq6qpjy5cc\"},{\"key\":\"code_id\",\"value\":\"2\"}]},{\"type\":\"wasm-xcvm.interpreter\",\"attributes\":[{\"key\":\"_contract_address\",\"value\":\"centauri1tlcw3llaxzs3jam7yheqxhr3k0n4y5sfrnah72n6aqt0rlp4xq6qpjy5cc\"},{\"key\":\"data\",\"value\":\"eyJ1c2VyX29yaWdpbiI6eyJuZXR3b3JrX2lkIjoyLCJ1c2VyX2lkIjoiNjM2NTZlNzQ2MTc1NzI2OTMxNjUzMDZlNjgzMzY0NjE3NjY0MzY2NDMzNzk2ODM3MzM3MzZjNjQ3NzZlNjQ3Nzc3Mzc3YTYzNjg2NTc0Nzc3MzYxNjUzODZkMzk3MjdhMzk3ODM2Njg2YzY3Mzc3NzM0Nzk3MzcyNzE2ODZkNjE3MTZhNzUifSwic2FsdCI6IjY0NjU2NjYxNzU2Yzc0In0=\"}]}],\"data\":\"CkNjZW50YXVyaTF0bGN3M2xsYXh6czNqYW03eWhlcXhocjNrMG40eTVzZnJuYWg3Mm42YXF0MHJscDR4cTZxcGp5NWNj\"}}}"}
{"level":"info","process":"centauri","replica":1,"message":"xcvm:: saved interpreter"}
````

Required for merge:
- [ ] `pr-workflow-check / draft-release-check` is ✅ success
- Other rules GitHub shows you, or can be read in
[configuration](../terraform/github.com/branches.tf)

Makes review faster:
- [x] PR title is my best effort to provide summary of changes and has
clear text to be part of release notes
- [x] I marked PR by `misc` label if it should not be in release notes
- [x] Linked Zenhub/Github/Slack/etc reference if one exists
- [x] I was clear on what type of deployment required to release my
changes (node, runtime, contract, indexer, on chain operation, frontend,
infrastructure) if any in PR title or description
- [x] Added reviewer into `Reviewers`
- [x] I tagged(`@`) or used other form of notification of one person who
I think can handle best review of this PR
- [x] I have proved that PR has no general regressions of relevant
features and processes required to release into production
- [x] Any dependency updates made, was done according guides from
relevant dependency
- Clicking all checkboxes 
- Adding detailed description of changes when it feels appropriate (for
example when PR is big)
  • Loading branch information
dzmitry-lahoda authored Aug 10, 2023
1 parent eccecf5 commit 4f5277f
Show file tree
Hide file tree
Showing 16 changed files with 598 additions and 247 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ RUN mkdir --parents /etc/nix/ && \
echo "cores = 32" >> /etc/nix/nix.conf && \
echo "allow-import-from-derivation = true" >> /etc/nix/nix.conf && \
echo "narinfo-cache-negative-ttl = 30" >> /etc/nix/nix.conf && \
echo "trusted-users = root vscode actiions-runner" >> /etc/nix/nix.conf && \
echo "trusted-users = root vscode actions-runner" >> /etc/nix/nix.conf && \
echo "substitute = true" >> /etc/nix/nix.conf && \
echo "substituters = https://cache.nixos.org/ https://composable-community.cachix.org/ https://devenv.cachix.org/ https://nix-community.cachix.org/" >> /etc/nix/nix.conf && \
echo "require-sigs = false" >> /etc/nix/nix.conf && \
Expand Down
6 changes: 3 additions & 3 deletions code/xcvm/cosmwasm/contracts/gateway/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ impl Auth<policy::Interpreter> {
info: &MessageInfo,
interpreter_origin: xc_core::InterpreterOrigin,
) -> Result<Self> {
let interpreter_address = state::interpreter::INTERPRETERS
.may_load(deps.storage, interpreter_origin)?
.map(|int| int.address);
let interpreter_address = state::interpreter::get_by_origin(deps, interpreter_origin)
.map(|int| int.address)
.ok();
Self::new(Some(&info.sender) == interpreter_address.as_ref())
}
}
Expand Down
87 changes: 21 additions & 66 deletions code/xcvm/cosmwasm/contracts/gateway/src/contract/execute.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
use crate::{
assets, auth,
contract::INSTANTIATE_INTERPRETER_REPLY_ID,
error::{ContractError, Result},
events::make_event,
msg,
interpreter, msg,
network::{self, load_this},
state,
};

use cosmwasm_std::{
entry_point, to_binary, wasm_execute, Addr, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Env,
MessageInfo, Reply, Response, StdError, StdResult, SubMsg, WasmMsg,
entry_point, wasm_execute, Addr, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo,
Response,
};
use cw20::{Cw20Contract, Cw20ExecuteMsg};
use cw_xc_interpreter::contract::{
XCVM_INTERPRETER_EVENT_DATA_ORIGIN, XCVM_INTERPRETER_EVENT_PREFIX,
};

use xc_core::{gateway::ConfigSubMsg, CallOrigin, Displayed, Funds, InterpreterOrigin};

Expand All @@ -25,7 +21,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: msg::ExecuteMsg)
match msg {
ExecuteMsg::Config(msg) => {
let auth = auth::Admin::authorise(deps.as_ref(), &info)?;
handle_config_msg(auth, deps, msg)
handle_config_msg(auth, deps, msg, env)
},

msg::ExecuteMsg::ExecuteProgram { execute_program, tip } =>
Expand All @@ -47,14 +43,15 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: msg::ExecuteMsg)
}
},
msg::ExecuteMsg::MessageHook(msg) => {
deps.api.debug(&serde_json_wasm::to_string(&msg)?);
let auth = auth::WasmHook::authorise(deps.storage, &env, &info, msg.from_network_id)?;
super::ibc::ics20::ics20_message_hook(auth, msg, env, info)
},
msg::ExecuteMsg::Shortcut(msg) => handle_shortcut(deps, env, info, msg),
}
}

fn handle_config_msg(auth: auth::Admin, deps: DepsMut, msg: ConfigSubMsg) -> Result {
fn handle_config_msg(auth: auth::Admin, deps: DepsMut, msg: ConfigSubMsg, env: Env) -> Result {
deps.api.debug(serde_json_wasm::to_string(&msg)?.as_str());
match msg {
ConfigSubMsg::ForceNetworkToNetwork(msg) =>
Expand All @@ -63,6 +60,8 @@ fn handle_config_msg(auth: auth::Admin, deps: DepsMut, msg: ConfigSubMsg) -> Res
ConfigSubMsg::ForceRemoveAsset { asset_id } =>
assets::force_remove_asset(auth, deps, asset_id),
ConfigSubMsg::ForceNetwork(msg) => network::force_network(auth, deps, msg),
ConfigSubMsg::ForceInstantiate { user_origin, salt } =>
interpreter::force_instantiate(auth, env.contract.address, deps, user_origin, salt),
}
}

Expand Down Expand Up @@ -154,10 +153,10 @@ pub(crate) fn handle_execute_program_privilleged(
) -> Result {
let config = load_this(deps.storage)?;
let interpreter_origin =
InterpreterOrigin { user_origin: call_origin.user(config.network_id), salt };
InterpreterOrigin { user_origin: call_origin.user(config.network_id), salt: salt.clone() };
let interpreter =
state::interpreter::INTERPRETERS.may_load(deps.storage, interpreter_origin.clone())?;
if let Some(state::interpreter::Interpreter { address }) = interpreter {
state::interpreter::get_by_origin(deps.as_ref(), interpreter_origin.clone()).ok();
if let Some(state::interpreter::Interpreter { address, .. }) = interpreter {
deps.api.debug("reusing existing interpreter and adding funds");
let response = send_funds_to_interpreter(deps.as_ref(), address.clone(), assets)?;
let wasm_msg = wasm_execute(
Expand All @@ -176,22 +175,17 @@ pub(crate) fn handle_execute_program_privilleged(
let interpreter_code_id = match config.gateway.expect("expected setup") {
msg::GatewayId::CosmWasm { interpreter_code_id, .. } => interpreter_code_id,
};
deps.api.debug("instantiating interpreter");
let admin = env.contract.address.clone();

let interpreter_instantiate_submessage = crate::interpreter::instantiate(
deps.as_ref(),
admin,
interpreter_code_id,
&interpreter_origin,
salt,
)?;

let instantiate_msg: CosmosMsg = WasmMsg::Instantiate {
// router is the default admin of a contract
admin: Some(env.contract.address.clone().into_string()),
code_id: interpreter_code_id,
msg: to_binary(&cw_xc_interpreter::msg::InstantiateMsg {
gateway_address: env.contract.address.clone().into_string(),
interpreter_origin: interpreter_origin.clone(),
})?,
funds: vec![],
label: format!("xcvm-interpreter-{interpreter_origin}"),
}
.into();

let interpreter_instantiate_submessage =
SubMsg::reply_on_success(instantiate_msg, INSTANTIATE_INTERPRETER_REPLY_ID);
// Secondly, call itself again with the same parameters, so that this functions goes
// into `Ok` state and properly executes the interpreter
let self_call_message: CosmosMsg = wasm_execute(
Expand Down Expand Up @@ -248,42 +242,3 @@ fn send_funds_to_interpreter(
}
Ok(response)
}

pub(crate) fn handle_instantiate_reply(deps: DepsMut, msg: Reply) -> StdResult<Response> {
let response = msg.result.into_result().map_err(StdError::generic_err)?;

// Catch the default `instantiate` event which contains `_contract_address` attribute that
// has the instantiated contract's address
let address = &response
.events
.iter()
.find(|event| event.ty == "instantiate")
.ok_or_else(|| StdError::not_found("instantiate event not found"))?
.attributes
.iter()
.find(|attr| &attr.key == "_contract_address")
.ok_or_else(|| StdError::not_found("_contract_address attribute not found"))?
.value;
let interpreter_address = deps.api.addr_validate(address)?;

// Interpreter provides `network_id, user_id` pair as an event for the router to know which
// pair is instantiated
let event_name = format!("wasm-{}", XCVM_INTERPRETER_EVENT_PREFIX);
let interpreter_origin = &response
.events
.iter()
.find(|event| event.ty.starts_with(&event_name))
.ok_or_else(|| StdError::not_found("interpreter event not found"))?
.attributes
.iter()
.find(|attr| &attr.key == XCVM_INTERPRETER_EVENT_DATA_ORIGIN)
.ok_or_else(|| StdError::not_found("no data is returned from 'xcvm_interpreter'"))?
.value;
let interpreter_origin =
xc_core::shared::decode_base64::<_, InterpreterOrigin>(interpreter_origin.as_str())?;

let interpreter = state::interpreter::Interpreter { address: interpreter_address };
state::interpreter::INTERPRETERS.save(deps.storage, interpreter_origin, &interpreter)?;

Ok(Response::new())
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use cosmwasm_std::{
};
use xc_core::{
gateway::{AssetItem, ExecuteMsg, ExecuteProgramMsg, GatewayId, OtherNetworkItem},
proto::decode_packet,
shared::{XcPacket, XcProgram},
transport::ibc::{to_cw_message, IbcRoute, XcMessageData},
AssetId, CallOrigin,
Expand All @@ -29,6 +28,7 @@ pub(crate) fn handle_bridge_forward(
info: MessageInfo,
msg: xc_core::gateway::BridgeForwardMsg,
) -> Result {
deps.api.debug(&serde_json_wasm::to_string(&msg)?);
ensure_eq!(msg.msg.assets.0.len(), 1, ContractError::ProgramCannotBeHandledByDestination);
// algorithm to handle for multihop
// 1. recurse on program until can with memo
Expand All @@ -45,7 +45,7 @@ pub(crate) fn handle_bridge_forward(
let (local_asset, amount) = packet.assets.0.get(0).expect("proved above");

let route = get_route(deps.storage, msg.to, *local_asset)?;

deps.api.debug(&serde_json_wasm::to_string(&route)?);
let mut event = make_event("bridge")
.add_attribute("to_network_id", msg.to.to_string())
.add_attribute(
Expand Down Expand Up @@ -121,7 +121,7 @@ pub(crate) fn ics20_message_hook(
env: Env,
info: MessageInfo,
) -> Result<Response, ContractError> {
let packet: XcPacket = decode_packet(&msg.data).map_err(ContractError::Protobuf)?;
let packet: XcPacket = msg.packet;

ensure_anonymous(&packet.program)?;
let call_origin = CallOrigin::Remote { user_origin: packet.user_origin };
Expand Down
4 changes: 2 additions & 2 deletions code/xcvm/cosmwasm/contracts/gateway/src/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use cw2::set_contract_version;
use cw_utils::ensure_from_older_version;
use xc_core::XCVMAck;

use self::{execute::handle_instantiate_reply, ibc::make_ibc_failure_event};
use self::ibc::make_ibc_failure_event;

const CONTRACT_NAME: &str = "composable:xcvm-gateway";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
Expand Down Expand Up @@ -57,7 +57,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response> {
match msg.id {
EXEC_PROGRAM_REPLY_ID => handle_exec_reply(msg),
INSTANTIATE_INTERPRETER_REPLY_ID =>
handle_instantiate_reply(deps, msg).map_err(ContractError::from),
crate::interpreter::handle_instantiate_reply(deps, msg).map_err(ContractError::from),
_ => Err(ContractError::UnknownReply),
}
}
Expand Down
130 changes: 130 additions & 0 deletions code/xcvm/cosmwasm/contracts/gateway/src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use crate::{
contract::INSTANTIATE_INTERPRETER_REPLY_ID,
error::{ContractError, Result},
events::make_event,
network::load_this,
state,
};
use cosmwasm_std::{
to_binary, Deps, DepsMut, Reply, Response, StdError, StdResult, SubMsg, WasmMsg,
};
use cw_xc_interpreter::contract::{
XCVM_INTERPRETER_EVENT_DATA_ORIGIN, XCVM_INTERPRETER_EVENT_PREFIX,
};
use xc_core::{CallOrigin, InterpreterOrigin};

use crate::{auth, prelude::*};

pub(crate) fn force_instantiate(
_: auth::Admin,
gateway: Addr,
deps: DepsMut,
user_origin: Addr,
salt: Option<String>,
) -> Result {
let config = load_this(deps.storage)?;
let interpreter_code_id = match config.gateway.expect("expected setup") {
GatewayId::CosmWasm { interpreter_code_id, .. } => interpreter_code_id,
};

let call_origin = CallOrigin::Local { user: user_origin };
let interpreter_origin = InterpreterOrigin {
user_origin: call_origin.user(config.network_id),
salt: salt.clone().map(|x| x.into_bytes()).unwrap_or_default(),
};
let msg = instantiate(
deps.as_ref(),
gateway,
interpreter_code_id,
&interpreter_origin,
salt.map(|x| x.into_bytes()).unwrap_or_default(),
)?;
Ok(Response::new().add_submessage(msg).add_event(make_event("interpreter.forced")))
}

pub fn instantiate(
deps: Deps,
admin: Addr,
interpreter_code_id: u64,
interpreter_origin: &InterpreterOrigin,
salt: Vec<u8>,
) -> Result<SubMsg, ContractError> {
let next_interpreter_id: u128 =
state::interpreter::INTERPRETERS_COUNT.load(deps.storage).unwrap_or_default() + 1;

let instantiate_msg = WasmMsg::Instantiate2 {
admin: Some(admin.clone().into_string()),
code_id: interpreter_code_id,
msg: to_binary(&cw_xc_interpreter::msg::InstantiateMsg {
gateway_address: admin.clone().into_string(),
interpreter_origin: interpreter_origin.clone(),
})?,
funds: vec![],
// and label has some unknown limits (including usage of special characters)
label: format!("xcvm_interpreter_{}", &next_interpreter_id),
// salt limit is 64 characters
salt: to_binary(&salt)?,
};
let interpreter_instantiate_submessage =
SubMsg::reply_on_success(instantiate_msg, INSTANTIATE_INTERPRETER_REPLY_ID);
Ok(interpreter_instantiate_submessage)
}

pub(crate) fn handle_instantiate_reply(deps: DepsMut, msg: Reply) -> StdResult<Response> {
deps.api.debug(&format!(
"xcvm:: {}",
serde_json_wasm::to_string(&msg).map_err(|e| StdError::generic_err(e.to_string()))?
));
let response = msg.result.into_result().map_err(StdError::generic_err)?;
// Catch the default `instantiate` event which contains `_contract_address` attribute that
// has the instantiated contract's address
let address = &response
.events
.iter()
.find(|event| event.ty == "instantiate")
.ok_or_else(|| StdError::not_found("instantiate event not found"))?
.attributes
.iter()
.find(|attr| &attr.key == "_contract_address")
.ok_or_else(|| StdError::not_found("_contract_address attribute not found"))?
.value;
let interpreter_address = deps.api.addr_validate(address)?;

// Interpreter provides `network_id, user_id` pair as an event for the router to know which
// pair is instantiated
let event_name = format!("wasm-{}", XCVM_INTERPRETER_EVENT_PREFIX);
let interpreter_origin = &response
.events
.iter()
.find(|event| event.ty.starts_with(&event_name))
.ok_or_else(|| StdError::not_found("interpreter event not found"))?
.attributes
.iter()
.find(|attr| &attr.key == XCVM_INTERPRETER_EVENT_DATA_ORIGIN)
.ok_or_else(|| StdError::not_found("no data is returned from 'xcvm_interpreter'"))?
.value;
let interpreter_origin =
xc_core::shared::decode_base64::<_, InterpreterOrigin>(interpreter_origin.as_str())?;

let interpreter_id: u128 =
state::interpreter::INTERPRETERS_COUNT.load(deps.storage).unwrap_or_default() + 1;
let interpreter = state::interpreter::Interpreter {
address: interpreter_address,
interpreter_id: interpreter_id.into(),
};

state::interpreter::INTERPRETERS_COUNT.save(deps.storage, &interpreter_id)?;
state::interpreter::INTERPRETERS.save(deps.storage, interpreter_id, &interpreter)?;
state::interpreter::INTERPRETERS_ORIGIN_TO_ID.save(
deps.storage,
interpreter_origin,
&interpreter_id,
)?;

deps.api.debug("xcvm:: saved interpreter");

Ok(Response::new().add_event(
make_event("xcvm.interpreter.instantiated")
.add_attribute("interpreter_id", interpreter_id.to_string()),
))
}
1 change: 1 addition & 0 deletions code/xcvm/cosmwasm/contracts/gateway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod auth;
pub mod contract;
pub mod error;
mod events;
pub mod interpreter;
mod network;
mod prelude;
pub mod state;
19 changes: 17 additions & 2 deletions code/xcvm/cosmwasm/contracts/gateway/src/state/interpreter.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
use xc_core::InterpreterOrigin;
use cosmwasm_std::{Deps, StdResult};
use cw_storage_plus::Item;
use xc_core::{Displayed, InterpreterOrigin};

use crate::prelude::*;

pub type InterpreterId = Displayed<u128>;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "std", derive(schemars::JsonSchema))]
pub(crate) struct Interpreter {
pub address: Addr,
pub interpreter_id: InterpreterId,
}

pub(crate) fn get_by_origin(deps: Deps, origin: InterpreterOrigin) -> StdResult<Interpreter> {
let id = INTERPRETERS_ORIGIN_TO_ID.load(deps.storage, origin)?;
INTERPRETERS.load(deps.storage, id)
}

pub(crate) const INTERPRETERS: Map<InterpreterOrigin, Interpreter> = Map::new("interpreters");
pub(crate) const INTERPRETERS_COUNT: Item<u128> = Item::new("interpreter_count");

pub(crate) const INTERPRETERS_ORIGIN_TO_ID: Map<InterpreterOrigin, u128> =
Map::new("interpreters_origin_to_id");

pub(crate) const INTERPRETERS: Map<u128, Interpreter> = Map::new("interpreters");
Loading

0 comments on commit 4f5277f

Please sign in to comment.