Skip to content

Commit

Permalink
[feature] #4126: Add chain_id to prevent replay attacks
Browse files Browse the repository at this point in the history
Signed-off-by: Marin Veršić <[email protected]>
  • Loading branch information
mversic committed Jan 9, 2024
1 parent f147295 commit 5cf8df7
Show file tree
Hide file tree
Showing 56 changed files with 854 additions and 646 deletions.
163 changes: 82 additions & 81 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ tokio = { workspace = true, features = ["macros", "signal"] }
once_cell = { workspace = true }
owo-colors = { workspace = true, features = ["supports-colors"] }
supports-color = { workspace = true }
rand = { workspace = true }

thread-local-panic-hook = { version = "0.1.0", optional = true }

Expand Down
26 changes: 14 additions & 12 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ impl Iroha {
let kura_thread_handler = Kura::start(Arc::clone(&kura));

let sumeragi = SumeragiHandle::start(SumeragiStartArgs {
chain_id: config.chain_id,
configuration: &config.sumeragi,
events_sender: events_sender.clone(),
wsv,
Expand All @@ -271,6 +272,7 @@ impl Iroha {
.start();

let gossiper = TransactionGossiper::from_configuration(
config.chain_id,
&config.sumeragi,
network.clone(),
Arc::clone(&queue),
Expand Down Expand Up @@ -300,6 +302,7 @@ impl Iroha {
let kiso = KisoHandle::new(config.clone());

let torii = Torii::new(
config.chain_id,
kiso.clone(),
&config.torii,
Arc::clone(&queue),
Expand Down Expand Up @@ -579,7 +582,7 @@ pub fn read_config(
.wrap_err("Invalid genesis configuration")?
{
Some(
GenesisNetwork::new(raw_block, &key_pair)
GenesisNetwork::new(raw_block, config.chain_id, &key_pair)
.wrap_err("Failed to construct the genesis")?,
)
} else {
Expand Down Expand Up @@ -630,21 +633,24 @@ mod tests {
use super::*;

fn config_factory() -> Result<ConfigurationProxy> {
let mut base = ConfigurationProxy::default();

let key_pair = KeyPair::generate()?;

base.public_key = Some(key_pair.public_key().clone());
base.private_key = Some(key_pair.private_key().clone());
let mut base = ConfigurationProxy {
chain_id: Some(0),

let torii = base.torii.as_mut().unwrap();
torii.p2p_addr = Some(socket_addr!(127.0.0.1:1337));
torii.api_url = Some(socket_addr!(127.0.0.1:1337));
public_key: Some(key_pair.public_key().clone()),
private_key: Some(key_pair.private_key().clone()),

..ConfigurationProxy::default()
};
let genesis = base.genesis.as_mut().unwrap();
genesis.private_key = Some(Some(key_pair.private_key().clone()));
genesis.public_key = Some(key_pair.public_key().clone());

let torii = base.torii.as_mut().unwrap();
torii.p2p_addr = Some(socket_addr!(127.0.0.1:1337));
torii.api_url = Some(socket_addr!(127.0.0.1:1337));

Ok(base)
}

Expand Down Expand Up @@ -686,10 +692,6 @@ mod tests {
// No need to check whether genesis.file is resolved - if not, genesis wouldn't be read
assert!(genesis.is_some());

assert_eq!(
config.kura.block_store_path.absolutize()?,
dir.path().join("storage")
);
assert_eq!(
config.snapshot.dir_path.absolutize()?,
dir.path().join("snapshots")
Expand Down
18 changes: 15 additions & 3 deletions cli/src/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use iroha_config::{
use iroha_crypto::{KeyPair, PublicKey};
use iroha_data_model::{peer::PeerId, prelude::*};
use iroha_primitives::unique_vec::UniqueVec;
use rand::Rng;

/// Get sample trusted peers. The public key must be the same as `configuration.public_key`
///
Expand Down Expand Up @@ -52,12 +53,19 @@ pub fn get_trusted_peers(public_key: Option<&PublicKey>) -> HashSet<PeerId> {
///
/// # Panics
/// - when [`KeyPair`] generation fails (rare case).
pub fn get_config_proxy(peers: UniqueVec<PeerId>, key_pair: Option<KeyPair>) -> ConfigurationProxy {
pub fn get_config_proxy(
peers: UniqueVec<PeerId>,
chain_id: Option<u16>,
key_pair: Option<KeyPair>,
) -> ConfigurationProxy {
let chain_id = chain_id.unwrap_or_else(|| rand::thread_rng().gen_range(0..u16::MAX));

let (public_key, private_key) = key_pair
.unwrap_or_else(|| KeyPair::generate().expect("Key pair generation failed"))
.into();
iroha_logger::info!(%public_key);
ConfigurationProxy {
chain_id: Some(chain_id),
public_key: Some(public_key.clone()),
private_key: Some(private_key.clone()),
sumeragi: Some(Box::new(iroha_config::sumeragi::ConfigurationProxy {
Expand Down Expand Up @@ -94,8 +102,12 @@ pub fn get_config_proxy(peers: UniqueVec<PeerId>, key_pair: Option<KeyPair>) ->
///
/// # Panics
/// - when [`KeyPair`] generation fails (rare case).
pub fn get_config(trusted_peers: UniqueVec<PeerId>, key_pair: Option<KeyPair>) -> Configuration {
get_config_proxy(trusted_peers, key_pair)
pub fn get_config(
trusted_peers: UniqueVec<PeerId>,
chain_id: Option<u16>,
key_pair: Option<KeyPair>,
) -> Configuration {
get_config_proxy(trusted_peers, chain_id, key_pair)
.build()
.expect("Iroha config should build as all required fields were provided")
}
Expand Down
4 changes: 2 additions & 2 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ iroha_version = { workspace = true, features = ["http"] }

attohttpc = { version = "0.26.1", default-features = false }
eyre = { workspace = true }
http = "0.2.9"
http = "0.2.11"
url = { workspace = true }
rand = { workspace = true }
serde = { workspace = true, features = ["derive"] }
Expand All @@ -71,7 +71,7 @@ parity-scale-codec = { workspace = true, default-features = false, features = ["
tokio = { workspace = true, features = ["rt"] }
tokio-tungstenite = { workspace = true }
tungstenite = { workspace = true }
futures-util = "0.3.28"
futures-util = "0.3.30"

[dev-dependencies]
iroha_wasm_builder = { workspace = true }
Expand Down
23 changes: 18 additions & 5 deletions client/benches/torii.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use iroha_client::{
use iroha_genesis::{GenesisNetwork, RawGenesisBlockBuilder};
use iroha_primitives::unique_vec;
use iroha_version::Encode;
use test_network::{get_key_pair, Peer as TestPeer, PeerBuilder, TestRuntime};
use test_network::{get_chain_id, get_key_pair, Peer as TestPeer, PeerBuilder, TestRuntime};
use tokio::runtime::Runtime;

const MINIMUM_SUCCESS_REQUEST_RATIO: f32 = 0.9;
Expand All @@ -30,7 +30,11 @@ fn get_genesis_key_pair(config: &iroha_config::iroha::Configuration) -> KeyPair

fn query_requests(criterion: &mut Criterion) {
let mut peer = <TestPeer>::new().expect("Failed to create peer");
let configuration = get_config(unique_vec![peer.id.clone()], Some(get_key_pair()));
let configuration = get_config(
unique_vec![peer.id.clone()],
Some(get_chain_id()),
Some(get_key_pair()),
);

let rt = Runtime::test();
let genesis = GenesisNetwork::new(
Expand All @@ -45,6 +49,7 @@ fn query_requests(criterion: &mut Criterion) {
construct_executor("../default_executor").expect("Failed to construct executor"),
)
.build(),
get_chain_id(),
&get_genesis_key_pair(&configuration),
)
.expect("genesis creation failed");
Expand Down Expand Up @@ -76,7 +81,8 @@ fn query_requests(criterion: &mut Criterion) {
quantity,
AssetId::new(asset_definition_id, account_id.clone()),
);
let mut client_config = iroha_client::samples::get_client_config(&get_key_pair());
let mut client_config =
iroha_client::samples::get_client_config(get_chain_id(), &get_key_pair());

client_config.torii_api_url = format!("http://{}", peer.api_address).parse().unwrap();

Expand Down Expand Up @@ -128,9 +134,14 @@ fn query_requests(criterion: &mut Criterion) {

fn instruction_submits(criterion: &mut Criterion) {
println!("instruction submits");
let chain_id = 0;
let rt = Runtime::test();
let mut peer = <TestPeer>::new().expect("Failed to create peer");
let configuration = get_config(unique_vec![peer.id.clone()], Some(get_key_pair()));
let configuration = get_config(
unique_vec![peer.id.clone()],
Some(get_chain_id()),
Some(get_key_pair()),
);
let genesis = GenesisNetwork::new(
RawGenesisBlockBuilder::default()
.domain("wonderland".parse().expect("Valid"))
Expand All @@ -143,6 +154,7 @@ fn instruction_submits(criterion: &mut Criterion) {
construct_executor("../default_executor").expect("Failed to construct executor"),
)
.build(),
chain_id,
&get_genesis_key_pair(&configuration),
)
.expect("failed to create genesis");
Expand All @@ -159,7 +171,8 @@ fn instruction_submits(criterion: &mut Criterion) {
.into();
let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into();
let asset_definition_id = AssetDefinitionId::new("xor".parse().expect("Valid"), domain_id);
let mut client_config = iroha_client::samples::get_client_config(&get_key_pair());
let mut client_config =
iroha_client::samples::get_client_config(get_chain_id(), &get_key_pair());
client_config.torii_api_url = format!("http://{}", peer.api_address).parse().unwrap();
let iroha_client = Client::new(&client_config).expect("Invalid client configuration");
thread::sleep(std::time::Duration::from_millis(5000));
Expand Down
4 changes: 3 additions & 1 deletion client/benches/tps/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ impl MeasurerUnit {

/// Spawn who periodically submits transactions
fn spawn_transaction_submitter(&self, shutdown_signal: mpsc::Receiver<()>) -> JoinHandle<()> {
let chain_id = 0;

let submitter = self.client.clone();
let interval_us_per_tx = self.config.interval_us_per_tx;
let instructions = self.instructions();
Expand All @@ -218,7 +220,7 @@ impl MeasurerUnit {
for instruction in instructions {
match shutdown_signal.try_recv() {
Err(mpsc::TryRecvError::Empty) => {
let mut transaction = TransactionBuilder::new(alice_id.clone())
let mut transaction = TransactionBuilder::new(chain_id, alice_id.clone())
.with_instructions([instruction]);
transaction.set_nonce(nonce); // Use nonce to avoid transaction duplication within the same thread

Expand Down
11 changes: 8 additions & 3 deletions client/examples/million_accounts_genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use iroha_data_model::isi::InstructionBox;
use iroha_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder};
use iroha_primitives::unique_vec;
use test_network::{
get_key_pair, wait_for_genesis_committed, Peer as TestPeer, PeerBuilder, TestRuntime,
get_chain_id, get_key_pair, wait_for_genesis_committed, Peer as TestPeer, PeerBuilder,
TestRuntime,
};
use tokio::runtime::Runtime;

Expand Down Expand Up @@ -36,9 +37,13 @@ fn generate_genesis(num_domains: u32) -> RawGenesisBlock {

fn main_genesis() {
let mut peer = <TestPeer>::new().expect("Failed to create peer");
let configuration = get_config(unique_vec![peer.id.clone()], Some(get_key_pair()));
let configuration = get_config(
unique_vec![peer.id.clone()],
Some(get_chain_id()),
Some(get_key_pair()),
);
let rt = Runtime::test();
let genesis = GenesisNetwork::new(generate_genesis(1_000_000_u32), &{
let genesis = GenesisNetwork::new(generate_genesis(1_000_000_u32), get_chain_id(), &{
let private_key = configuration
.genesis
.private_key
Expand Down
7 changes: 6 additions & 1 deletion client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ impl_query_output! {
)]
#[display(fmt = "{}@{torii_url}", "key_pair.public_key()")]
pub struct Client {
/// Unique id of the blockchain. Used for simple replay attack protection.
pub chain_id: u16,
/// Url for accessing iroha node
pub torii_url: Url,
/// Accounts keypair
Expand Down Expand Up @@ -440,6 +442,7 @@ impl Client {
}

Ok(Self {
chain_id: configuration.chain_id,
torii_url: configuration.torii_api_url.clone(),
key_pair: KeyPair::new(
configuration.public_key.clone(),
Expand All @@ -466,7 +469,7 @@ impl Client {
instructions: impl Into<Executable>,
metadata: UnlimitedMetadata,
) -> Result<SignedTransaction> {
let tx_builder = TransactionBuilder::new(self.account_id.clone());
let tx_builder = TransactionBuilder::new(self.chain_id, self.account_id.clone());

let mut tx_builder = match instructions.into() {
Executable::Instructions(instructions) => tx_builder.with_instructions(instructions),
Expand Down Expand Up @@ -1664,6 +1667,7 @@ mod tests {
let (public_key, private_key) = KeyPair::generate().unwrap().into();

let cfg = ConfigurationProxy {
chain_id: Some(0),
public_key: Some(public_key),
private_key: Some(private_key),
account_id: Some(
Expand Down Expand Up @@ -1706,6 +1710,7 @@ mod tests {
};

let cfg = ConfigurationProxy {
chain_id: Some(0),
public_key: Some(
"ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0"
.parse()
Expand Down
3 changes: 2 additions & 1 deletion client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ pub mod samples {
};

/// Get sample client configuration.
pub fn get_client_config(key_pair: &KeyPair) -> Configuration {
pub fn get_client_config(chain_id: u16, key_pair: &KeyPair) -> Configuration {
let (public_key, private_key) = key_pair.clone().into();
ConfigurationProxy {
chain_id: Some(chain_id),
public_key: Some(public_key),
private_key: Some(private_key),
account_id: Some(
Expand Down
10 changes: 6 additions & 4 deletions client/tests/integration/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,12 @@ fn find_rate_and_make_exchange_isi_should_succeed() {
alice_id.clone(),
);

let grant_asset_transfer_tx = TransactionBuilder::new(asset_id.account_id().clone())
.with_instructions([allow_alice_to_transfer_asset])
.sign(owner_keypair)
.expect("Failed to sign seller transaction");
let chain_id = 0;
let grant_asset_transfer_tx =
TransactionBuilder::new(chain_id, asset_id.account_id().clone())
.with_instructions([allow_alice_to_transfer_asset])
.sign(owner_keypair)
.expect("Failed to sign seller transaction");

test_client
.submit_transaction_blocking(&grant_asset_transfer_tx)
Expand Down
2 changes: 1 addition & 1 deletion client/tests/integration/burn_public_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn submit(
eyre::Result<HashOf<TransactionPayload>>,
) {
let tx = if let Some((account_id, keypair)) = submitter {
TransactionBuilder::new(account_id)
TransactionBuilder::new(0, account_id)
.with_instructions(instructions)
.sign(keypair)
.unwrap()
Expand Down
4 changes: 2 additions & 2 deletions client/tests/integration/domain_owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> {

// register asset definitions by "bob@kingdom" so he is owner of it
let coin = AssetDefinition::quantity(coin_id.clone());
let transaction = TransactionBuilder::new(bob_id.clone())
let transaction = TransactionBuilder::new(0, bob_id.clone())
.with_instructions([Register::asset_definition(coin)])
.sign(bob_keypair)?;
test_client.submit_transaction_blocking(&transaction)?;
Expand Down Expand Up @@ -181,7 +181,7 @@ fn domain_owner_asset_permissions() -> Result<()> {
// register asset definitions by "bob@kingdom" so he is owner of it
let coin = AssetDefinition::quantity(coin_id.clone());
let store = AssetDefinition::store(store_id.clone());
let transaction = TransactionBuilder::new(bob_id.clone())
let transaction = TransactionBuilder::new(0, bob_id.clone())
.with_instructions([
Register::asset_definition(coin),
Register::asset_definition(store),
Expand Down
Loading

0 comments on commit 5cf8df7

Please sign in to comment.