Skip to content

Commit

Permalink
Transaction signing (#345)
Browse files Browse the repository at this point in the history
* Initial implementation for tx sign

* Adding logic to build the tx as part of the chain. Added broadcast tx to chain. #47

* Added keys restore command boileplate for abscissa. Restore key functionality not implemented yet #47

* Implemented changes to support tx signing (#47):

* Implemented very basic keybase to store keys (memory store)

* Logic to restore key (private/public) from mnemonic

* Added keystore to the chain

* Implemented working 'keys restore' command on the relayer

* Refactoring keybase structure (#47)

* Initial logic to send message (#47)

* Got the logic to sign but MsgConnectionOpenInit test against stargate-4 not working (#47)

* MsgConnectionOpenInit tx working logic! (#47)

* Added option to tx raw conn-init to specify and read key file content (#47)

* Logic to parse the key_seed.json file passed as tx raw parameter working (#47)

* Added support to specify key file and account sequence for the tax raw conn-init command (#47)

* Adding instructions on how to submit a transaction (#345)

* Fixing format issues (#345)

* Fixing tests (#345)

* Update relayer/src/tx/client.rs

I had this in place so I could test. OK to change it back

Co-authored-by: Anca Zamfir <ancazamfir@users.noreply.github.com>

* Update relayer/src/tx/connection.rs

OK, didn't know where to get it from, thanks.

Co-authored-by: Anca Zamfir <ancazamfir@users.noreply.github.com>

Co-authored-by: Anca Zamfir <ancazamfir@users.noreply.github.com>
  • Loading branch information
andynog and ancazamfir committed Oct 30, 2020
1 parent 052b574 commit 2b5029b
Show file tree
Hide file tree
Showing 24 changed files with 748 additions and 184 deletions.
1 change: 1 addition & 0 deletions modules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ prost-types = "0.6.1"
bytes = "0.6.0"
dyn-clonable = "0.9.0"
regex = "1"
bech32 = "0.7.2"

[dependencies.tendermint]
version = "0.17.0-rc1"
Expand Down
4 changes: 4 additions & 0 deletions modules/src/ics03_connection/msgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub mod test_util {
"0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".to_string()
}

pub fn get_dummy_bech32_account() -> String {
"cosmos1wxeyh7zgn4tctjzs0vtqpc6p5cxq5t2muzl7ng".to_string()
}

pub fn get_dummy_account_id() -> AccountId {
AccountId::from_str(&get_dummy_account_id_raw()).unwrap()
}
Expand Down
27 changes: 21 additions & 6 deletions modules/src/ics03_connection/msgs/conn_open_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use crate::ics03_connection::connection::{validate_version, Counterparty};
use crate::ics03_connection::error::{Error, Kind};
use crate::ics24_host::identifier::{ClientId, ConnectionId};
use crate::tx_msg::Msg;
use std::str::FromStr;

use bech32::{FromBase32, ToBase32};

/// Message type for the `MsgConnectionOpenInit` message.
pub const TYPE_MSG_CONNECTION_OPEN_INIT: &str = "connection_open_init";
Expand Down Expand Up @@ -84,6 +85,19 @@ impl TryFrom<RawMsgConnectionOpenInit> for MsgConnectionOpenInit {
type Error = anomaly::Error<Kind>;

fn try_from(msg: RawMsgConnectionOpenInit) -> Result<Self, Self::Error> {
let (_hrp, data) = bech32::decode(&msg.signer).map_err(|e| {
Kind::InvalidAddress
.context("Error decoding signer ".to_string() + &msg.signer + ":" + &e.to_string())
})?;
let addr_bytes = Vec::<u8>::from_base32(&data).map_err(|e| {
Kind::InvalidAddress
.context("Error converting from bech32: ".to_string() + &e.to_string())
})?;
let acct = AccountId::try_from(addr_bytes).map_err(|e| {
Kind::InvalidAddress
.context("Error converting to account ID: ".to_string() + &e.to_string())
})?;

Ok(Self {
connection_id: msg
.connection_id
Expand All @@ -98,19 +112,20 @@ impl TryFrom<RawMsgConnectionOpenInit> for MsgConnectionOpenInit {
.ok_or_else(|| Kind::MissingCounterparty)?
.try_into()?,
version: validate_version(msg.version).map_err(|e| Kind::InvalidVersion.context(e))?,
signer: AccountId::from_str(msg.signer.as_str())
.map_err(|e| Kind::InvalidSigner.context(e))?,
signer: acct,
})
}
}

impl From<MsgConnectionOpenInit> for RawMsgConnectionOpenInit {
fn from(ics_msg: MsgConnectionOpenInit) -> Self {
// The msg needs to send the bech32 account as the signer
let addr = bech32::encode("cosmos", ics_msg.signer.to_base32()).unwrap();
RawMsgConnectionOpenInit {
client_id: ics_msg.client_id.as_str().to_string(),
connection_id: ics_msg.connection_id.as_str().to_string(),
counterparty: Some(ics_msg.counterparty.into()),
signer: ics_msg.signer.to_string(),
signer: addr,
version: ics_msg.version,
}
}
Expand All @@ -121,7 +136,7 @@ pub mod test_util {
use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenInit as RawMsgConnectionOpenInit;

use crate::ics03_connection::msgs::test_util::{
get_dummy_account_id_raw, get_dummy_counterparty,
get_dummy_bech32_account, get_dummy_counterparty,
};

/// Returns a dummy message, for testing only.
Expand All @@ -132,7 +147,7 @@ pub mod test_util {
connection_id: "srcconnection".to_string(),
counterparty: Some(get_dummy_counterparty()),
version: "1.0.0".to_string(),
signer: get_dummy_account_id_raw(),
signer: get_dummy_bech32_account(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion modules/src/tx_msg.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use tendermint::account::Id as AccountId;

pub trait Msg {
pub trait Msg: Sized {
type ValidationError: std::error::Error;

// TODO -- clarify what is this function supposed to do & its connection to ICS26 routing mod.
Expand Down
1 change: 1 addition & 0 deletions relayer-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ serde_derive = "1.0.116"
sled = "0.34.4"
prost = "0.6.1"
prost-types = { version = "0.6.1" }
hex = "0.4"

[dependencies.tendermint]
version = "0.17.0-rc1"
Expand Down
88 changes: 81 additions & 7 deletions relayer-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,88 @@
# relayer-cli
# Relayer (Rust)

`relayer-cli` is an application.
This is the repository for the IBC Relayer built in Rust.

For updates please check the [releases on the ibc-rs repository](https://github.com/informalsystems/ibc-rs/releases)

## Getting Started

This application is authored using [Abscissa], a Rust application framework.
In order to run the Relayer please ensure you have [Rust installed on your machine](https://www.rust-lang.org/tools/install)

### Submitting an IBC transaction

The `tx raw conn-init` command works now. Signing the message is working and the gaia chain (stargate-4) accepts the transaction.

The command accepts two parameters that allows you to send a transaction:

* **signer-key** (-k) -> specify a key file (name and location) that will be used by the signer. This key seed file has a mnemonic (seed phrase) that can be used to retrieve the private key (BIP-39) used to sign the transaction.
* **account_sequence** (-s) -> this is the account sequence value, basically every time a tx is committed by the account this number increases.

#### Steps to testing the transaction:

* Start two chains using the `dev-env` script from the [ovrclk/relayer](https://github.com/ovrclk/relayer) (make sure to checkout stargate-4 version)
* After you run the script, the Go relayer will create a `data` folder for the chains. Open the key seed file `./data/ibc1/key_seed.json` for chain `ibc-1` and look for the account value


{
"name":"user",
"type":"local",
"address":"cosmos1tqzwwr5hrnq2ceg5fg52m720m50xpfy08at7l9",
"pubkey":"cosmospub1addwnpepq08wntxejcla5hd93stgudw02htdpa9vu5a2ds8xkvmgrkrrpwlj6sdhkz6",
"mnemonic":"[MNEMONIC WORDS"}
}


* In order to find the account sequence run the command below:

For the address value after `gaiad query account` use the `address` from the step above.

`$ gaiad query account cosmos1tqzwwr5hrnq2ceg5fg52m720m50xpfy08at7l9 --home ./data/ibc1 --chain-id ibc1 --node tcp://localhost:26557`

This will return a JSON with a sequence number at the end. Make a note of that, you will need this number as an argument to the transaction command.


'@type': /cosmos.auth.v1beta1.BaseAccount
account_number: "0"
address: cosmos1tqzwwr5hrnq2ceg5fg52m720m50xpfy08at7l9
pub_key:
'@type': /cosmos.crypto.secp256k1.PubKey
key: A87prNmWP9pdpYwWjjXPVdbQ9KzlOqbA5rM2gdhjC78t
sequence: "12"


* Run the transaction command. This will try to initialize an `ibczeroconn2` connection on chain `ibc1`

`$ cargo run --bin relayer -- -c ./relayer-cli/tests/fixtures/two_chains.toml tx raw conn-init ibc0 ibc1 ibczeroclient ibconeclient ibczeroconn2 ibconeconn -s 12 -k key_seed.json`

If you get an empty response it means the tx worked

`conn init, result: []`

* Check if the connection was created on `ibc-1` using the Golang relayer

`$ rly query connections ibc1 | jq .`

If you see an entry in the JSON file that points to the `ibczeroconn2` connection with state `STATE_INIT` it confirms that the transaction worked:


{
"id": "ibczeroconn21",
"client_id": "ibczeroclient",
"versions": [
"\n\u00011\u0012\rORDER_ORDERED\u0012\u000fORDER_UNORDERED"
],
"state": "STATE_INIT",
"counterparty": {
"client_id": "ibconeclient",
"connection_id": "ibconeconn",
"prefix": {
"key_prefix": "aWJj"
}
}
},



For more information, see:

[Documentation]

[Abscissa]: https://github.com/iqlusioninc/abscissa
[Documentation]: https://docs.rs/abscissa_core/
9 changes: 7 additions & 2 deletions relayer-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! application's configuration file.

mod config;
mod keys;
mod light;
mod listen;
mod query;
Expand All @@ -15,8 +16,8 @@ mod v0;
mod version;

use self::{
config::ConfigCmd, light::LightCmd, listen::ListenCmd, query::QueryCmd, start::StartCmd,
tx::TxCmd, v0::V0Cmd, version::VersionCmd,
config::ConfigCmd, keys::KeysCmd, light::LightCmd, listen::ListenCmd, query::QueryCmd,
start::StartCmd, tx::TxCmd, v0::V0Cmd, version::VersionCmd,
};

use crate::config::Config;
Expand Down Expand Up @@ -64,6 +65,10 @@ pub enum CliCmd {
/// The `light` subcommand
#[options(help = "basic functionality for managing the lite clients")]
Light(LightCmd),

/// The `keys` subcommand
#[options(help = "manage keys in the relayer for each chain")]
Keys(KeysCmd),
}

/// This trait allows you to define how application configuration is loaded.
Expand Down
16 changes: 16 additions & 0 deletions relayer-cli/src/commands/keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//! `keys` subcommand
use abscissa_core::{Command, Help, Options, Runnable};

mod restore;

/// `keys` subcommand
#[derive(Command, Debug, Options, Runnable)]
pub enum KeysCmd {
/// The `help` subcommand
#[options(help = "get usage information")]
Help(Help<Self>),

/// The `keys restore` subcommand
#[options(help = "keys restore")]
Restore(restore::KeyRestoreCmd),
}
74 changes: 74 additions & 0 deletions relayer-cli/src/commands/keys/restore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::application::app_config;
use abscissa_core::{Command, Options, Runnable};
use relayer::config::Config;

use crate::error::{Error, Kind};
use crate::prelude::*;
use relayer::keys::restore::{restore_key, KeysRestoreOptions};

#[derive(Clone, Command, Debug, Options)]
pub struct KeyRestoreCmd {
#[options(free, help = "identifier of the chain")]
chain_id: Option<String>,

#[options(free, help = "the key name")]
name: Option<String>,

#[options(free, help = "mnemonic to add key")]
mnemonic: Option<String>,
}

impl KeyRestoreCmd {
fn validate_options(&self, config: &Config) -> Result<KeysRestoreOptions, String> {
let chain_id = self
.chain_id
.clone()
.ok_or_else(|| "missing chain identifier".to_string())?;

let chain_config = config
.chains
.iter()
.find(|c| c.id == chain_id.parse().unwrap())
.ok_or_else(|| {
"Invalid chain identifier. Cannot retrieve the chain configuration".to_string()
})?;

let key_name = self
.name
.clone()
.ok_or_else(|| "missing key name".to_string())?;

let mnemonic_words = self
.mnemonic
.clone()
.ok_or_else(|| "missing mnemonic".to_string())?;

Ok(KeysRestoreOptions {
name: key_name,
mnemonic: mnemonic_words,
chain_config: chain_config.clone(),
})
}
}

impl Runnable for KeyRestoreCmd {
fn run(&self) {
let config = app_config();

let opts = match self.validate_options(&config) {
Err(err) => {
status_err!("invalid options: {}", err);
return;
}
Ok(result) => result,
};

let res: Result<Vec<u8>, Error> =
restore_key(opts).map_err(|e| Kind::Keys.context(e).into());

match res {
Ok(r) => status_info!("key restore result: ", "{:?}", hex::encode(r)),
Err(e) => status_info!("key restore failed: ", "{}", e),
}
}
}
23 changes: 22 additions & 1 deletion relayer-cli/src/commands/tx/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use relayer::tx::client::{create_client, CreateClientOptions};
use crate::application::app_config;
use crate::error::{Error, Kind};
use crate::prelude::*;
use std::fs;
use std::path::Path;

#[derive(Clone, Command, Debug, Options)]
pub struct TxCreateClientCmd {
Expand All @@ -19,10 +21,27 @@ pub struct TxCreateClientCmd {
help = "identifier of the client to be created on destination chain"
)]
dest_client_id: Option<String>,

#[options(free, help = "key file for the signer")]
signer_key: Option<String>,
}

impl TxCreateClientCmd {
fn validate_options(&self, config: &Config) -> Result<CreateClientOptions, String> {
// Get content of key seed file
let key_filename = self
.signer_key
.clone()
.ok_or_else(|| "missing signer key file".to_string())?;

let key_file = Path::new(&key_filename).exists();
if !key_file {
return Err("cannot find key file specified".to_string());
}

let key_file_contents = fs::read_to_string(key_filename)
.expect("Something went wrong reading the key seed file");

let dest_chain_id = self
.dest_chain_id
.clone()
Expand Down Expand Up @@ -56,6 +75,7 @@ impl TxCreateClientCmd {
dest_client_id,
dest_chain_config: dest_chain_config.clone(),
src_chain_config: src_chain_config.clone(),
signer_key: key_file_contents,
})
}
}
Expand All @@ -73,7 +93,8 @@ impl Runnable for TxCreateClientCmd {
};
status_info!("Message", "{:?}", opts);

let res: Result<(), Error> = create_client(opts).map_err(|e| Kind::Tx.context(e).into());
let res: Result<Vec<u8>, Error> =
create_client(opts).map_err(|e| Kind::Tx.context(e).into());

match res {
Ok(receipt) => status_info!("client created, result: ", "{:?}", receipt),
Expand Down
Loading

0 comments on commit 2b5029b

Please sign in to comment.