Skip to content

Commit

Permalink
Add unit test for saving wallet with pending blobs
Browse files Browse the repository at this point in the history
  • Loading branch information
ndr-ds committed Oct 23, 2024
1 parent 6f44b9f commit 648f7be
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 37 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions linera-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ non-fungible.workspace = true
proptest.workspace = true
reqwest = { workspace = true, features = ["json"] }
social.workspace = true
tempfile.workspace = true
test-case.workspace = true
test-log = { workspace = true, features = ["trace"] }
test-strategy.workspace = true
Expand Down
4 changes: 0 additions & 4 deletions linera-client/src/chain_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ use tracing::{debug, error, info, warn, Instrument as _};

use crate::{wallet::Wallet, Error};

#[cfg(test)]
#[path = "unit_tests/chain_listener.rs"]
mod tests;

#[derive(Debug, Default, Clone, clap::Args)]
pub struct ChainListenerConfig {
/// Do not create blocks automatically to receive incoming messages. Instead, wait for
Expand Down
64 changes: 61 additions & 3 deletions linera-client/src/client_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use async_trait::async_trait;
use futures::Future;
use linera_base::{
crypto::KeyPair,
data_types::{BlockHeight, Timestamp},
identifiers::{Account, ChainId},
data_types::{Blob, BlockHeight, Timestamp},
identifiers::{Account, BlobId, ChainId},
ownership::ChainOwnership,
time::{Duration, Instant},
};
Expand Down Expand Up @@ -184,6 +184,41 @@ where
}
}

#[cfg(with_testing)]
pub fn new_test(storage: S, wallet: W) -> Self {
let send_recv_timeout = Duration::from_millis(4000);
let retry_delay = Duration::from_millis(1000);
let max_retries = 10;

let node_options = NodeOptions {
send_timeout: send_recv_timeout,
recv_timeout: send_recv_timeout,
retry_delay,
max_retries,
};
let node_provider = NodeProvider::new(node_options);
let delivery = CrossChainMessageDelivery::new(true);
let chain_ids = wallet.chain_ids();
let name = match chain_ids.len() {
0 => "Client node".to_string(),
1 => format!("Client node for {:.8}", chain_ids[0]),
n => format!("Client node for {:.8} and {} others", chain_ids[0], n - 1),
};
let client = Client::new(node_provider, storage, 10, delivery, false, chain_ids, name);

ClientContext {
client: Arc::new(client),
wallet: WalletState::new(wallet),
send_timeout: send_recv_timeout,
recv_timeout: send_recv_timeout,
retry_delay,
max_retries,
chain_listeners: JoinSet::default(),
blanket_message_policy: BlanketMessagePolicy::Accept,
restrict_chain_ids_to: None,
}
}

/// Retrieve the default account. Current this is the common account of the default
/// chain.
pub fn default_account(&self) -> Account {
Expand Down Expand Up @@ -268,6 +303,29 @@ where
chain_id: ChainId,
key_pair: Option<KeyPair>,
timestamp: Timestamp,
) -> Result<(), Error> {
self.update_wallet_for_new_chain_internal(chain_id, key_pair, timestamp, BTreeMap::new())
.await
}

#[cfg(test)]
pub async fn update_wallet_for_new_chain_with_pending_blobs(
&mut self,
chain_id: ChainId,
key_pair: Option<KeyPair>,
timestamp: Timestamp,
pending_blobs: BTreeMap<BlobId, Blob>,
) -> Result<(), Error> {
self.update_wallet_for_new_chain_internal(chain_id, key_pair, timestamp, pending_blobs)
.await
}

async fn update_wallet_for_new_chain_internal(
&mut self,
chain_id: ChainId,
key_pair: Option<KeyPair>,
timestamp: Timestamp,
pending_blobs: BTreeMap<BlobId, Blob>,
) -> Result<(), Error> {
if self.wallet.get(chain_id).is_none() {
self.mutate_wallet(|w| {
Expand All @@ -278,7 +336,7 @@ where
timestamp,
next_block_height: BlockHeight::ZERO,
pending_block: None,
pending_blobs: BTreeMap::new(),
pending_blobs,
})
})
.await?;
Expand Down
3 changes: 3 additions & 0 deletions linera-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ pub mod storage;
pub mod util;
pub mod wallet;

#[cfg(test)]
mod unit_tests;

pub use error::Error;
32 changes: 2 additions & 30 deletions linera-client/src/unit_tests/chain_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@ use linera_core::{
node::CrossChainMessageDelivery,
test_utils::{MemoryStorageBuilder, NodeProvider, StorageBuilder as _, TestBuilder},
};
use linera_execution::{system::Recipient, ResourceControlPolicy};
use linera_rpc::{
config::{NetworkProtocol, ValidatorPublicNetworkPreConfig},
simple::TransportProtocol,
};
use linera_execution::system::Recipient;
use linera_storage::{DbStorage, TestClock};
use linera_views::memory::MemoryStore;
use rand::SeedableRng as _;

use super::util::make_genesis_config;
use crate::{
chain_listener::{self, ChainListener, ChainListenerConfig, ClientContext as _},
config::{CommitteeConfig, GenesisConfig, ValidatorConfig},
wallet::{UserChain, Wallet},
Error,
};
Expand Down Expand Up @@ -108,30 +104,6 @@ impl chain_listener::ClientContext for ClientContext {
}
}

fn make_genesis_config(builder: &TestBuilder<MemoryStorageBuilder>) -> GenesisConfig {
let network = ValidatorPublicNetworkPreConfig {
protocol: NetworkProtocol::Simple(TransportProtocol::Tcp),
host: "localhost".to_string(),
port: 8080,
};
let validator_names = builder.initial_committee.validators().keys();
let validators = validator_names
.map(|name| ValidatorConfig {
name: *name,
network: network.clone(),
})
.collect();
let mut genesis_config = GenesisConfig::new(
CommitteeConfig { validators },
builder.admin_id(),
Timestamp::from(0),
ResourceControlPolicy::default(),
"test network".to_string(),
);
genesis_config.chains.extend(builder.genesis_chains());
genesis_config
}

/// Tests that the chain listener, if there is a message in the inbox, will continue requesting
/// timeout certificates until it becomes the leader and can process the inbox.
#[test_log::test(tokio::test)]
Expand Down
11 changes: 11 additions & 0 deletions linera-client/src/unit_tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

#[cfg(test)]
mod util;

#[cfg(test)]
mod chain_listener;

#[cfg(test)]
mod wallet;
36 changes: 36 additions & 0 deletions linera-client/src/unit_tests/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use linera_base::data_types::Timestamp;
use linera_core::test_utils::{MemoryStorageBuilder, TestBuilder};
use linera_execution::ResourceControlPolicy;
use linera_rpc::{
config::{NetworkProtocol, ValidatorPublicNetworkPreConfig},
simple::TransportProtocol,
};

use crate::config::{CommitteeConfig, GenesisConfig, ValidatorConfig};

pub fn make_genesis_config(builder: &TestBuilder<MemoryStorageBuilder>) -> GenesisConfig {
let network = ValidatorPublicNetworkPreConfig {
protocol: NetworkProtocol::Simple(TransportProtocol::Tcp),
host: "localhost".to_string(),
port: 8080,
};
let validator_names = builder.initial_committee.validators().keys();
let validators = validator_names
.map(|name| ValidatorConfig {
name: *name,
network: network.clone(),
})
.collect();
let mut genesis_config = GenesisConfig::new(
CommitteeConfig { validators },
builder.admin_id(),
Timestamp::from(0),
ResourceControlPolicy::default(),
"test network".to_string(),
);
genesis_config.chains.extend(builder.genesis_chains());
genesis_config
}
61 changes: 61 additions & 0 deletions linera-client/src/unit_tests/wallet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::collections::BTreeMap;

use anyhow::anyhow;
use linera_base::{
crypto::KeyPair,
data_types::{Amount, Blob},
identifiers::{ChainDescription, ChainId},
};
use linera_core::test_utils::{MemoryStorageBuilder, StorageBuilder, TestBuilder};
use rand::SeedableRng as _;

use super::util::make_genesis_config;
use crate::{client_context::ClientContext, config::WalletState, wallet::Wallet};

/// Tests wether we can correctly save a wallet that contains pending blobs.
#[test_log::test(tokio::test)]
async fn test_save_wallet_with_pending_blobs() -> anyhow::Result<()> {
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let storage_builder = MemoryStorageBuilder::default();
let clock = storage_builder.clock().clone();
let mut builder = TestBuilder::new(storage_builder, 4, 1).await?;
let description = ChainDescription::Root(0);
let chain_id = ChainId::from(description);
builder.add_initial_chain(description, Amount::ONE).await?;
let storage = builder.make_storage().await?;
let key_pair = KeyPair::generate_from(&mut rng);

let genesis_config = make_genesis_config(&builder);

let tmp_dir = tempfile::tempdir()?;
let mut config_dir = tmp_dir.into_path();
config_dir.push("linera");
if !config_dir.exists() {
tracing::debug!("{} does not exist, creating", config_dir.display());
fs_err::create_dir(&config_dir)?;
tracing::debug!("{} created.", config_dir.display());
}
let wallet_path = config_dir.join("wallet.json");
if wallet_path.exists() {
return Err(anyhow!("Wallet already exists!"));
}
let wallet =
WalletState::create_from_file(&wallet_path, Wallet::new(genesis_config, Some(37)))?;
let mut context = ClientContext::new_test(storage, wallet);
let blob = Blob::new_data(b"blob".to_vec());
let mut pending_blobs = BTreeMap::new();
pending_blobs.insert(blob.id(), blob);
context
.update_wallet_for_new_chain_with_pending_blobs(
chain_id,
Some(key_pair),
clock.current_time(),
pending_blobs,
)
.await?;
context.save_wallet().await?;
Ok(())
}

0 comments on commit 648f7be

Please sign in to comment.