Skip to content
This repository has been archived by the owner on Jun 3, 2020. It is now read-only.

Commit

Permalink
cosmos-stdtx: JSON serializers for computing/signing StdSignMsg
Browse files Browse the repository at this point in the history
  • Loading branch information
tony-iqlusion committed Jan 27, 2020
1 parent 1dc247f commit be27fdf
Show file tree
Hide file tree
Showing 14 changed files with 286 additions and 62 deletions.
7 changes: 5 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ jobs:
build:
docker:
- image: tendermint/kms:build-2019-06-05-v0 # bump cache keys when modifying this
environment:
CARGO_INCREMENTAL: 0
RUSTFLAGS: -D warnings
steps:
- checkout
- restore_cache:
key: cache-2019-06-05-v0 # bump save_cache key below too
key: cache-2020-01-27-v0 # bump save_cache key below too
- run:
name: Install Rust 1.39.0 # TODO: update Rust in the upstream Docker image
command: |
Expand Down Expand Up @@ -61,7 +64,7 @@ jobs:
cargo build --features=softsign
TMKMS_BIN=./target/debug/tmkms sh tests/support/run-harness-tests.sh
- save_cache:
key: cache-2019-06-05-v0 # bump restore_cache key above too
key: cache-2020-01-27-v0 # bump restore_cache key above too
paths:
- "~/.cargo"
- "./target"
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cosmos-stdtx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ circle-ci = { repository = "tendermint/kms" }

[dependencies]
anomaly = "0.1"
ecdsa = { version = "0.4", features = ["k256"] }
prost-amino = "0.5"
prost-amino-derive = "0.5"
rust_decimal = "1.1"
serde = { version = "1", features = ["serde_derive"] }
serde_json = "1"
sha2 = "0.8"
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
Expand Down
5 changes: 5 additions & 0 deletions cosmos-stdtx/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ impl Address {

Ok((hrp, Address(addr.as_slice().try_into().unwrap())))
}

/// Encode this address as Bech32
pub fn to_bech32(&self, hrp: &str) -> String {
bech32::encode(hrp, &self.0)
}
}

impl AsRef<[u8]> for Address {
Expand Down
39 changes: 31 additions & 8 deletions cosmos-stdtx/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
//! Transaction message type (i.e `sdk.Msg`)
mod builder;
mod field;
mod value;

pub use self::{builder::Builder, value::Value};
pub use self::{builder::Builder, field::Field, value::Value};
pub use rust_decimal::Decimal;

use crate::type_name::TypeName;
use crate::{Schema, TypeName};
use prost_amino::encode_length_delimiter as encode_leb128; // Little-endian Base 128
use std::{collections::BTreeMap, iter::FromIterator};

/// Tags are indexes which identify message fields
pub type Tag = u64;

/// Fields in the message
pub type Field = (Tag, Value);

/// Transaction message type (i.e. [`sdk.Msg`]).
/// These serve as the payload for [`StdTx`] transactions.
///
Expand All @@ -30,16 +29,40 @@ pub struct Msg {
}

impl Msg {
/// Compute `serde_json::Value` representing a `sdk.Msg`
pub fn to_json_value(&self, schema: &Schema) -> serde_json::Value {
// `BTreeMap` ensures fields are ordered for Cosmos's Canonical JSON
let mut values = BTreeMap::new();

for field in &self.fields {
values.insert(
field.name().to_string(),
field.value().to_json_value(schema),
);
}

let mut json = serde_json::Map::new();
json.insert(
"type".to_owned(),
serde_json::Value::String(self.type_name.to_string()),
);
json.insert(
"value".to_owned(),
serde_json::Map::from_iter(values.into_iter()).into(),
);
serde_json::Value::Object(json)
}

/// Encode this message in the Amino wire format
pub fn to_amino_bytes(&self) -> Vec<u8> {
let mut result = self.type_name.amino_prefix();

for (tag, value) in &self.fields {
for field in &self.fields {
// Compute the field prefix, which encodes the tag and wire type code
let prefix = *tag << 3 | value.wire_type();
let prefix = field.tag() << 3 | field.value().wire_type();
encode_leb128(prefix as usize, &mut result).expect("LEB128 encoding error");

let mut encoded_value = value.to_amino_bytes();
let mut encoded_value = field.value().to_amino_bytes();
encode_leb128(encoded_value.len(), &mut result).expect("LEB128 encoding error");
result.append(&mut encoded_value);
}
Expand Down
57 changes: 25 additions & 32 deletions cosmos-stdtx/src/msg/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ pub struct Builder<'a> {
type_name: TypeName,

/// Bech32 prefix for account addresses
acc_address_prefix: Option<String>,
acc_prefix: String,

/// Bech32 prefix for validator consensus addresses
val_address_prefix: Option<String>,
val_prefix: String,

/// Fields in the message
fields: Vec<Field>,
Expand All @@ -45,14 +45,11 @@ impl<'a> Builder<'a> {
)
})?;

let acc_address_prefix = schema.acc_address_prefix().map(ToString::to_string);
let val_address_prefix = schema.val_address_prefix().map(ToString::to_string);

Ok(Self {
schema_definition,
type_name,
acc_address_prefix,
val_address_prefix,
acc_prefix: schema.acc_prefix().to_owned(),
val_prefix: schema.val_prefix().to_owned(),
fields: vec![],
})
}
Expand All @@ -68,7 +65,7 @@ impl<'a> Builder<'a> {
.schema_definition
.get_field_tag(field_name, ValueType::SdkAccAddress)?;

let field = (tag, Value::SdkAccAddress(address));
let field = Field::new(tag, field_name.clone(), Value::SdkAccAddress(address));

self.fields.push(field);
Ok(self)
Expand All @@ -82,15 +79,13 @@ impl<'a> Builder<'a> {
) -> Result<&mut Self, Error> {
let (hrp, address) = Address::from_bech32(addr_bech32)?;

if let Some(prefix) = &self.acc_address_prefix {
ensure!(
&hrp == prefix,
ErrorKind::Address,
"invalid account address prefix: `{}` (expected `{}`)",
hrp,
prefix,
);
}
ensure!(
hrp == self.acc_prefix,
ErrorKind::Address,
"invalid account address prefix: `{}` (expected `{}`)",
hrp,
self.acc_prefix,
);

self.acc_address(field_name, address)
}
Expand All @@ -106,7 +101,7 @@ impl<'a> Builder<'a> {
.schema_definition
.get_field_tag(field_name, ValueType::SdkDecimal)?;

let field = (tag, Value::SdkDecimal(value.into()));
let field = Field::new(tag, field_name.clone(), Value::SdkDecimal(value.into()));

self.fields.push(field);
Ok(self)
Expand All @@ -123,7 +118,7 @@ impl<'a> Builder<'a> {
.schema_definition
.get_field_tag(field_name, ValueType::SdkValAddress)?;

let field = (tag, Value::SdkValAddress(address));
let field = Field::new(tag, field_name.clone(), Value::SdkValAddress(address));

self.fields.push(field);
Ok(self)
Expand All @@ -137,15 +132,13 @@ impl<'a> Builder<'a> {
) -> Result<&mut Self, Error> {
let (hrp, address) = Address::from_bech32(addr_bech32)?;

if let Some(prefix) = &self.val_address_prefix {
ensure!(
&hrp == prefix,
ErrorKind::Address,
"invalid validator address prefix: `{}` (expected `{}`)",
hrp,
prefix,
);
}
ensure!(
hrp == self.val_prefix,
ErrorKind::Address,
"invalid validator address prefix: `{}` (expected `{}`)",
hrp,
self.val_prefix,
);

self.val_address(field_name, address)
}
Expand All @@ -160,17 +153,17 @@ impl<'a> Builder<'a> {
.schema_definition
.get_field_tag(field_name, ValueType::String)?;

let field = (tag, Value::String(s.into()));
let field = Field::new(tag, field_name.clone(), Value::String(s.into()));

self.fields.push(field);
Ok(self)
}

/// Consume this builder and output a message
pub fn into_msg(self) -> Msg {
pub fn to_msg(&self) -> Msg {
Msg {
type_name: self.type_name,
fields: self.fields,
type_name: self.type_name.clone(),
fields: self.fields.clone(),
}
}
}
43 changes: 43 additions & 0 deletions cosmos-stdtx/src/msg/field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Message fields
use super::{Tag, Value};
use crate::type_name::TypeName;

/// Message fields
#[derive(Clone, Debug)]
pub struct Field {
/// Field number to use as the key in an Amino message.
tag: Tag,

/// Name of this field
name: TypeName,

/// Amino type to serialize this field as
value: Value,
}

impl Field {
/// Create a new message field
pub fn new(tag: Tag, name: TypeName, value: impl Into<Value>) -> Self {
Self {
tag,
name,
value: value.into(),
}
}

/// Get this field's [`Tag`]
pub fn tag(&self) -> Tag {
self.tag
}

/// Get this field's [`TypeName`]
pub fn name(&self) -> &TypeName {
&self.name
}

/// Get this field's [`Value`]
pub fn value(&self) -> &Value {
&self.value
}
}
28 changes: 27 additions & 1 deletion cosmos-stdtx/src/msg/value.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Message values
use crate::{address::Address, schema::ValueType};
use crate::{
address::Address,
schema::{Schema, ValueType},
};
use rust_decimal::Decimal;

/// Message values - data contained in fields of a message
Expand Down Expand Up @@ -54,4 +57,27 @@ impl Value {
Value::String(s) => s.as_bytes().to_vec(),
}
}

/// Encode this value as a [`serde_json::Value`]
pub(super) fn to_json_value(&self, schema: &Schema) -> serde_json::Value {
serde_json::Value::String(match self {
Value::SdkAccAddress(addr) => addr.to_bech32(schema.acc_prefix()),
// TODO(tarcieri): check that decimals are being encoded correctly
Value::SdkDecimal(decimal) => decimal.to_string(),
Value::SdkValAddress(addr) => addr.to_bech32(schema.val_prefix()),
Value::String(s) => s.clone(),
})
}
}

impl From<Decimal> for Value {
fn from(dec: Decimal) -> Value {
Value::SdkDecimal(dec)
}
}

impl From<String> for Value {
fn from(s: String) -> Value {
Value::String(s)
}
}
23 changes: 10 additions & 13 deletions cosmos-stdtx/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ pub struct Schema {
namespace: TypeName,

/// Bech32 prefix for account addresses
acc_address_prefix: Option<String>,
acc_prefix: String,

/// Bech32 prefix for validator consensus addresses
val_address_prefix: Option<String>,
val_prefix: String,

/// Schema definitions
#[serde(rename = "definition")]
Expand All @@ -59,17 +59,14 @@ impl Schema {
/// Create a new [`Schema`] with the given `StdTx` namespace and [`Definition`] set
pub fn new(
namespace: TypeName,
acc_address_prefix: Option<impl AsRef<str>>,
val_address_prefix: Option<impl AsRef<str>>,
acc_prefix: impl Into<String>,
val_prefix: impl Into<String>,
definitions: impl Into<Vec<Definition>>,
) -> Self {
let acc_address_prefix = acc_address_prefix.as_ref().map(|s| s.as_ref().to_owned());
let val_address_prefix = val_address_prefix.as_ref().map(|s| s.as_ref().to_owned());

Self {
namespace,
acc_address_prefix,
val_address_prefix,
acc_prefix: acc_prefix.into(),
val_prefix: val_prefix.into(),
definitions: definitions.into(),
}
}
Expand All @@ -88,13 +85,13 @@ impl Schema {
}

/// Get the Bech32 prefix for account addresses
pub fn acc_address_prefix(&self) -> Option<&str> {
self.acc_address_prefix.as_ref().map(AsRef::as_ref)
pub fn acc_prefix(&self) -> &str {
self.acc_prefix.as_ref()
}

/// Get the Bech32 prefix for validator addresses
pub fn val_address_prefix(&self) -> Option<&str> {
self.val_address_prefix.as_ref().map(AsRef::as_ref)
pub fn val_prefix(&self) -> &str {
self.val_prefix.as_ref()
}

/// [`Definition`] types found in this [`Schema`]
Expand Down
Loading

0 comments on commit be27fdf

Please sign in to comment.