Skip to content
This repository has been archived by the owner on Nov 20, 2024. It is now read-only.

Commit

Permalink
refactor: Refactor transaction validation and message handling (#24)
Browse files Browse the repository at this point in the history
* fix: Remove cosmos specific error type

* feat: Add decorators

* feat: Add decorators to runtime

* feat: Add validate basic decorator

* feat: Add tx timeout decoration

* feat: Add validation of memo decoration

* refactor: Simplify code

* refactor: Refactor modules

* refactor: Refactor decorator trait

* feat: Add msgs

* fix: Fix parameter types bug

* style: Reformat

* feat: Add handlers

* feat: Add get signers

* refactor: Refactor ante handlers path

* feat: Add chain id to pallet cosmos

* refactor: Refactor self contained check

* fix: Remove decoding cosmos tx from client

* refactor: Refactor amino sign doc

* feat: Add signer doc

* refactor: Refactor get sign doc hash

* test: Add sign doc test

* refactor: Refactor features

* refactor: Remove unused error types

---------

Co-authored-by: Jungyong Um <[email protected]>
  • Loading branch information
code0xff and Jungyong Um authored Jul 8, 2024
1 parent e37b180 commit 34656a5
Show file tree
Hide file tree
Showing 34 changed files with 1,207 additions and 639 deletions.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ resolver = "2"
members = [
"client/rpc",
"frame/cosmos",
"frame/cosmos/modules",
"frame/cosmos/modules/auth",
"frame/cosmos/modules/bank",
"frame/cosmos-accounts",
"primitives/account",
"primitives/cosmos",
Expand All @@ -21,6 +24,7 @@ cosmrs = "0.15.0"
futures = "0.3.28"
hex = "0.4.3"
jsonrpsee = "0.22.5"
log = { version = "0.4.21", default-features = false }
num_enum = { version = "0.7.2", default-features = false }
parity-scale-codec = { version = "3.2.0", default-features = false }
ripemd = { version = "0.1.3", default-features = false }
Expand Down Expand Up @@ -96,6 +100,10 @@ pallet-cosmos = { path = "frame/cosmos", default-features = false }
pallet-cosmos-accounts = { path = "frame/cosmos-accounts", default-features = false }
horizon-template-runtime = { path = "template/runtime", default-features = false }

pallet-cosmos-auth = { path = "frame/cosmos/modules/auth", default-features = false}
pallet-cosmos-bank = { path = "frame/cosmos/modules/bank", default-features = false}
pallet-cosmos-modules = { path = "frame/cosmos/modules", default-features = false}

[profile.release]
panic = "unwind"

Expand Down
2 changes: 0 additions & 2 deletions client/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ jsonrpsee = { workspace = true, features = ["server", "macros"] }
hex = { workspace = true }

# Substrate
sc-chain-spec = { workspace = true }
sc-transaction-pool-api = { workspace = true }
sp-api = { workspace = true, features = ["std"] }
sp-blockchain = { workspace = true }
sp-core = { workspace = true, features = ["std"] }
sp-runtime = { workspace = true, features = ["std"] }

# Horizon
hp-cosmos = { workspace = true, features = ["std"] }
hp-rpc = { workspace = true, features = ["std"] }
17 changes: 4 additions & 13 deletions client/rpc/src/cosm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use crate::{internal_err, request_err};
use crate::internal_err;
use futures::future::TryFutureExt;
use jsonrpsee::{
core::{async_trait, RpcResult},
Expand All @@ -37,19 +37,14 @@ pub trait CosmApi {
}

pub struct Cosm<B: BlockT, C, P> {
chain_spec: Box<dyn sc_chain_spec::ChainSpec>,
pool: Arc<P>,
client: Arc<C>,
_marker: PhantomData<B>,
}

impl<B: BlockT, C, P> Cosm<B, C, P> {
pub fn new(
chain_spec: Box<dyn sc_chain_spec::ChainSpec>,
pool: Arc<P>,
client: Arc<C>,
) -> Self {
Self { chain_spec, pool, client, _marker: Default::default() }
pub fn new(pool: Arc<P>, client: Arc<C>) -> Self {
Self { pool, client, _marker: Default::default() }
}
}

Expand All @@ -66,15 +61,11 @@ where
async fn broadcast_tx(&self, tx_bytes: Bytes) -> RpcResult<H256> {
use hp_rpc::ConvertTxRuntimeApi;

let chain_id = self.chain_spec.id();
hp_cosmos::Tx::decode(&tx_bytes, chain_id.as_bytes()).map_err(|e| {
request_err(format!("invalid transaction error; code: {}, message: {}", e as u8, e))
})?;
let block_hash = self.client.info().best_hash;
let extrinsic = self
.client
.runtime_api()
.convert_tx(block_hash, tx_bytes.to_vec(), chain_id.as_bytes().to_vec())
.convert_tx(block_hash, tx_bytes.to_vec())
.map_err(|_| internal_err("cannot access runtime api"))?;
let tx_hash = H256(sha2_256(&tx_bytes));
self.pool
Expand Down
4 changes: 4 additions & 0 deletions frame/cosmos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
log = { workspace = true }

# Substrate
frame-support = { workspace = true }
Expand All @@ -25,6 +26,8 @@ sp-std = { workspace = true }
hp-cosmos = { workspace = true, features = ["with-codec"] }
hp-io = { workspace = true }

pallet-cosmos-modules = { workspace = true }

[features]
default = ["std"]
std = [
Expand All @@ -39,4 +42,5 @@ std = [
"sp-std/std",
"hp-cosmos/std",
"hp-io/std",
"pallet-cosmos-modules/std"
]
22 changes: 22 additions & 0 deletions frame/cosmos/modules/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "pallet-cosmos-modules"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/noirhq/horizon/"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
# Substrate
frame-support = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

# Horizon
hp-cosmos = { workspace = true, features = ["with-codec"] }

[features]
default = ["std"]
std = ["frame-support/std", "sp-runtime/std", "sp-std/std", "hp-cosmos/std"]
33 changes: 33 additions & 0 deletions frame/cosmos/modules/auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "pallet-cosmos-auth"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/noirhq/horizon/"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
frame-system = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

hp-cosmos = { workspace = true }
hp-io = { workspace = true }
pallet-cosmos = { workspace = true }
pallet-cosmos-modules = { workspace = true }

[features]
default = ["std"]
std = [
"frame-system/std",
"sp-core/std",
"sp-runtime/std",
"sp-std/std",
"hp-cosmos/std",
"hp-io/std",
"pallet-cosmos/std",
"pallet-cosmos-modules/std"
]
78 changes: 78 additions & 0 deletions frame/cosmos/modules/auth/src/basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// This file is part of Horizon.

// Copyright (C) 2023 Haderech Pte. Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use hp_cosmos::Tx;
use pallet_cosmos_modules::ante::AnteHandler;
use sp_runtime::{
traits::Get,
transaction_validity::{InvalidTransaction, TransactionValidityError},
SaturatedConversion,
};
use sp_std::marker::PhantomData;

pub struct ValidateBasicHandler<T>(PhantomData<T>);

impl<T> AnteHandler for ValidateBasicHandler<T>
where
T: frame_system::Config,
{
fn handle(tx: &Tx) -> Result<(), TransactionValidityError> {
if tx.signatures.is_empty() {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadProof));
}
if tx.auth_info.signer_infos.len() != tx.signatures.len() {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner));
}

Ok(())
}
}

pub struct TxTimeoutHeightHandler<T>(PhantomData<T>);

impl<T> AnteHandler for TxTimeoutHeightHandler<T>
where
T: frame_system::Config,
{
fn handle(tx: &Tx) -> Result<(), TransactionValidityError> {
if tx.body.timeout_height > 0 &&
frame_system::Pallet::<T>::block_number().saturated_into::<u64>() >
tx.body.timeout_height
{
return Err(TransactionValidityError::Invalid(InvalidTransaction::Stale));
}

Ok(())
}
}

pub struct ValidateMemoHandler<T>(PhantomData<T>);

impl<T> AnteHandler for ValidateMemoHandler<T>
where
T: pallet_cosmos::Config,
{
fn handle(tx: &Tx) -> Result<(), TransactionValidityError> {
if tx.body.memo.len().saturated_into::<u64>() > T::MaxMemoCharacters::get() {
// TODO: Consider use InvalidTransaction::Custom
return Err(TransactionValidityError::Invalid(InvalidTransaction::Call));
}

Ok(())
}
}
23 changes: 23 additions & 0 deletions frame/cosmos/modules/auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// This file is part of Horizon.

// Copyright (C) 2023 Haderech Pte. Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::comparison_chain, clippy::large_enum_variant)]

pub mod basic;
pub mod sigverify;
115 changes: 115 additions & 0 deletions frame/cosmos/modules/auth/src/sigverify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// This file is part of Horizon.

// Copyright (C) 2023 Haderech Pte. Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use hp_cosmos::{AccountId, PublicKey, SignerPublicKey, Tx};
use hp_io::crypto::secp256k1_ecdsa_verify;
use pallet_cosmos_modules::ante::AnteHandler;
use sp_core::{sha2_256, Get, H160};
use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError};
use sp_std::marker::PhantomData;

pub struct SigVerificationHandler<T>(PhantomData<T>);

impl<T> AnteHandler for SigVerificationHandler<T>
where
T: frame_system::Config + pallet_cosmos::Config,
{
fn handle(tx: &Tx) -> Result<(), TransactionValidityError> {
let signatures = &tx.signatures;

let mut signers = sp_std::vec::Vec::<AccountId>::new();
for msg in &tx.body.messages {
if let Some(msg_signers) =
hp_io::signers::get_msg_any_signers(&msg.type_url, &msg.value)
{
for msg_signer in msg_signers {
if !signers.contains(&msg_signer) {
signers.push(msg_signer);
}
}
}
}
if let Some(fee_payer) = &tx.auth_info.fee.payer {
if !signers.contains(fee_payer) {
signers.push(fee_payer.clone());
}
}

if signatures.len() != signers.len() {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner));
}

let signer_infos = &tx.auth_info.signer_infos;
if signatures.len() != signer_infos.len() {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner));
}

for (i, sig) in signatures.iter().enumerate() {
let signer = signers
.get(i)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;

// TODO: Support other types of Signers as well
let signer_info = signer_infos
.get(i)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;

if let Some(SignerPublicKey::Single(PublicKey::Secp256k1(public_key))) =
signer_info.public_key
{
let address: H160 = hp_io::crypto::ripemd160(&sha2_256(&public_key)).into();
if signer.address != address {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner));
}

let (account, _) = pallet_cosmos::Pallet::<T>::account(&signer.address);
if signer_info.sequence > account.sequence {
return Err(TransactionValidityError::Invalid(InvalidTransaction::Future));
} else if signer_info.sequence < account.sequence {
return Err(TransactionValidityError::Invalid(InvalidTransaction::Stale));
}

let chain_id = T::ChainId::get();
let hash = match &signer_info.mode_info {
hp_cosmos::ModeInfo::Single(single) => match single.mode {
hp_cosmos::SignMode::Direct =>
hp_io::tx::get_signer_doc_bytes(&tx.raw, &chain_id, 0u64),

hp_cosmos::SignMode::LegacyAminoJson =>
hp_io::tx::get_amino_signer_doc_bytes(
&tx.raw,
&chain_id,
0u64,
signer_info.sequence,
),
_ => None,
},
}
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;

if !secp256k1_ecdsa_verify(sig, &hash, &public_key) {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadProof));
}
} else {
return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner));
}
}

Ok(())
}
}
Loading

0 comments on commit 34656a5

Please sign in to comment.