dncurrency/nano/core_test/wallet.cpp
Piotr Wójcik 2e9f5bd1f4
Enable running core test suite in parallel (#4246)
* Move `test::get_available_port` to `test::system` class

* Use ephemeral ports in tests

* Fix database directories for core tests

* Fix rpc tests

* Use `SO_REUSEADDR`

* Fix `ipv6_bind_send_ipv4`

* Use 0 to bind to any port by default

* Fix `network` tests

* Fix `socket` tests

* Fix `ipc` tests

* Fix remaining tests

* Raise the file limit soft-cap when running tests in parallel.

* Importing gtest-parallel as a submodule.

---------

Co-authored-by: Colin LeMahieu <clemahieu@gmail.com>
2023-06-13 17:27:58 +02:00

1249 lines
43 KiB
C++

#include <nano/crypto_lib/random_pool.hpp>
#include <nano/lib/threading.hpp>
#include <nano/node/lmdb/wallet_value.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
using namespace std::chrono_literals;
unsigned constexpr nano::wallet_store::version_current;
TEST (wallet, no_special_keys_accounts)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::keypair key1;
ASSERT_FALSE (wallet.exists (transaction, key1.pub));
wallet.insert_adhoc (transaction, key1.prv);
ASSERT_TRUE (wallet.exists (transaction, key1.pub));
for (uint64_t account = 0; account < nano::wallet_store::special_count; account++)
{
nano::account account_l (account);
ASSERT_FALSE (wallet.exists (transaction, account_l));
}
}
TEST (wallet, no_key)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::keypair key1;
nano::raw_key prv1;
ASSERT_TRUE (wallet.fetch (transaction, key1.pub, prv1));
ASSERT_TRUE (wallet.valid_password (transaction));
}
TEST (wallet, fetch_locked)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_TRUE (wallet.valid_password (transaction));
nano::keypair key1;
ASSERT_EQ (key1.pub, wallet.insert_adhoc (transaction, key1.prv));
auto key2 (wallet.deterministic_insert (transaction));
ASSERT_FALSE (key2.is_zero ());
nano::raw_key key3;
key3 = 1;
wallet.password.value_set (key3);
ASSERT_FALSE (wallet.valid_password (transaction));
nano::raw_key key4;
ASSERT_TRUE (wallet.fetch (transaction, key1.pub, key4));
ASSERT_TRUE (wallet.fetch (transaction, key2, key4));
}
TEST (wallet, retrieval)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::keypair key1;
ASSERT_TRUE (wallet.valid_password (transaction));
wallet.insert_adhoc (transaction, key1.prv);
nano::raw_key prv1;
ASSERT_FALSE (wallet.fetch (transaction, key1.pub, prv1));
ASSERT_TRUE (wallet.valid_password (transaction));
ASSERT_EQ (key1.prv, prv1);
wallet.password.values[0]->bytes[16] ^= 1;
nano::raw_key prv2;
ASSERT_TRUE (wallet.fetch (transaction, key1.pub, prv2));
ASSERT_FALSE (wallet.valid_password (transaction));
}
TEST (wallet, empty_iteration)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
auto i (wallet.begin (transaction));
auto j (wallet.end ());
ASSERT_EQ (i, j);
}
TEST (wallet, one_item_iteration)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::keypair key1;
wallet.insert_adhoc (transaction, key1.prv);
for (auto i (wallet.begin (transaction)), j (wallet.end ()); i != j; ++i)
{
ASSERT_EQ (key1.pub, nano::uint256_union (i->first));
nano::raw_key password;
wallet.wallet_key (password, transaction);
nano::raw_key key;
key.decrypt (nano::wallet_value (i->second).key, password, (nano::uint256_union (i->first)).owords[0].number ());
ASSERT_EQ (key1.prv, key);
}
}
TEST (wallet, two_item_iteration)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
nano::keypair key1;
nano::keypair key2;
ASSERT_NE (key1.pub, key2.pub);
std::unordered_set<nano::public_key> pubs;
std::unordered_set<nano::raw_key> prvs;
nano::kdf kdf{ nano::dev::network_params.kdf_work };
{
auto transaction (env.tx_begin_write ());
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
wallet.insert_adhoc (transaction, key1.prv);
wallet.insert_adhoc (transaction, key2.prv);
for (auto i (wallet.begin (transaction)), j (wallet.end ()); i != j; ++i)
{
pubs.insert (i->first);
nano::raw_key password;
wallet.wallet_key (password, transaction);
nano::raw_key key;
key.decrypt (nano::wallet_value (i->second).key, password, (i->first).owords[0].number ());
prvs.insert (key);
}
}
ASSERT_EQ (2, pubs.size ());
ASSERT_EQ (2, prvs.size ());
ASSERT_NE (pubs.end (), pubs.find (key1.pub));
ASSERT_NE (prvs.end (), prvs.find (key1.prv));
ASSERT_NE (pubs.end (), pubs.find (key2.pub));
ASSERT_NE (prvs.end (), prvs.find (key2.prv));
}
TEST (wallet, insufficient_spend_one)
{
nano::test::system system (1);
nano::keypair key1;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto block (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 500));
ASSERT_NE (nullptr, block);
ASSERT_EQ (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, nano::dev::constants.genesis_amount));
}
TEST (wallet, spend_all_one)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
nano::block_hash latest1 (node1.latest (nano::dev::genesis_key.pub));
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2;
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, std::numeric_limits<nano::uint128_t>::max ()));
auto transaction (node1.store.tx_begin_read ());
auto info2 = node1.ledger.account_info (transaction, nano::dev::genesis_key.pub);
ASSERT_NE (latest1, info2->head);
auto block (node1.store.block.get (transaction, info2->head));
ASSERT_NE (nullptr, block);
ASSERT_EQ (latest1, block->previous ());
ASSERT_TRUE (info2->balance.is_zero ());
ASSERT_EQ (0, node1.balance (nano::dev::genesis_key.pub));
}
TEST (wallet, send_async)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2;
std::thread thread ([&system] () {
ASSERT_TIMELY (10s, system.nodes[0]->balance (nano::dev::genesis_key.pub).is_zero ());
});
std::atomic<bool> success (false);
system.wallet (0)->send_async (nano::dev::genesis_key.pub, key2.pub, std::numeric_limits<nano::uint128_t>::max (), [&success] (std::shared_ptr<nano::block> const & block_a) { ASSERT_NE (nullptr, block_a); success = true; });
thread.join ();
ASSERT_TIMELY (2s, success);
}
TEST (wallet, spend)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
nano::block_hash latest1 (node1.latest (nano::dev::genesis_key.pub));
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2;
// Sending from empty accounts should always be an error. Accounts need to be opened with an open block, not a send block.
ASSERT_EQ (nullptr, system.wallet (0)->send_action (0, key2.pub, 0));
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, std::numeric_limits<nano::uint128_t>::max ()));
auto transaction (node1.store.tx_begin_read ());
auto info2 = node1.ledger.account_info (transaction, nano::dev::genesis_key.pub);
ASSERT_TRUE (info2);
ASSERT_NE (latest1, info2->head);
auto block (node1.store.block.get (transaction, info2->head));
ASSERT_NE (nullptr, block);
ASSERT_EQ (latest1, block->previous ());
ASSERT_TRUE (info2->balance.is_zero ());
ASSERT_EQ (0, node1.balance (nano::dev::genesis_key.pub));
}
TEST (wallet, change)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2;
auto block1 (system.nodes[0]->rep_block (nano::dev::genesis_key.pub));
ASSERT_FALSE (block1.is_zero ());
ASSERT_NE (nullptr, system.wallet (0)->change_action (nano::dev::genesis_key.pub, key2.pub));
auto block2 (system.nodes[0]->rep_block (nano::dev::genesis_key.pub));
ASSERT_FALSE (block2.is_zero ());
ASSERT_NE (block1, block2);
}
TEST (wallet, partial_spend)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2;
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, 500));
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max () - 500, system.nodes[0]->balance (nano::dev::genesis_key.pub));
}
TEST (wallet, spend_no_previous)
{
nano::test::system system (1);
{
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto transaction (system.nodes[0]->store.tx_begin_read ());
auto info1 = system.nodes[0]->ledger.account_info (transaction, nano::dev::genesis_key.pub);
ASSERT_TRUE (info1);
for (auto i (0); i < 50; ++i)
{
nano::keypair key;
system.wallet (0)->insert_adhoc (key.prv);
}
}
nano::keypair key2;
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, 500));
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max () - 500, system.nodes[0]->balance (nano::dev::genesis_key.pub));
}
TEST (wallet, find_none)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::account account (1000);
ASSERT_EQ (wallet.end (), wallet.find (transaction, account));
}
TEST (wallet, find_existing)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::keypair key1;
ASSERT_FALSE (wallet.exists (transaction, key1.pub));
wallet.insert_adhoc (transaction, key1.prv);
ASSERT_TRUE (wallet.exists (transaction, key1.pub));
auto existing (wallet.find (transaction, key1.pub));
ASSERT_NE (wallet.end (), existing);
++existing;
ASSERT_EQ (wallet.end (), existing);
}
TEST (wallet, rekey)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::raw_key password;
wallet.password.value (password);
ASSERT_TRUE (password.is_zero ());
ASSERT_FALSE (init);
nano::keypair key1;
wallet.insert_adhoc (transaction, key1.prv);
nano::raw_key prv1;
wallet.fetch (transaction, key1.pub, prv1);
ASSERT_EQ (key1.prv, prv1);
ASSERT_FALSE (wallet.rekey (transaction, "1"));
wallet.password.value (password);
nano::raw_key password1;
wallet.derive_key (password1, transaction, "1");
ASSERT_EQ (password1, password);
nano::raw_key prv2;
wallet.fetch (transaction, key1.pub, prv2);
ASSERT_EQ (key1.prv, prv2);
*wallet.password.values[0] = 2;
ASSERT_TRUE (wallet.rekey (transaction, "2"));
}
TEST (account, encode_zero)
{
nano::account number0{};
std::string str0;
number0.encode_account (str0);
/*
* Handle different lengths for "xrb_" prefixed and "nano_" prefixed accounts
*/
ASSERT_EQ ((str0.front () == 'x') ? 64 : 65, str0.size ());
ASSERT_EQ (65, str0.size ());
nano::account number1;
ASSERT_FALSE (number1.decode_account (str0));
ASSERT_EQ (number0, number1);
}
TEST (account, encode_all)
{
nano::account number0;
number0.decode_hex ("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
std::string str0;
number0.encode_account (str0);
/*
* Handle different lengths for "xrb_" prefixed and "nano_" prefixed accounts
*/
ASSERT_EQ ((str0.front () == 'x') ? 64 : 65, str0.size ());
nano::account number1;
ASSERT_FALSE (number1.decode_account (str0));
ASSERT_EQ (number0, number1);
}
TEST (account, encode_fail)
{
nano::account number0{};
std::string str0;
number0.encode_account (str0);
str0[16] ^= 1;
nano::account number1;
ASSERT_TRUE (number1.decode_account (str0));
}
TEST (wallet, hash_password)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
nano::raw_key hash1;
wallet.derive_key (hash1, transaction, "");
nano::raw_key hash2;
wallet.derive_key (hash2, transaction, "");
ASSERT_EQ (hash1, hash2);
nano::raw_key hash3;
wallet.derive_key (hash3, transaction, "a");
ASSERT_NE (hash1, hash3);
}
TEST (fan, reconstitute)
{
nano::raw_key value0 (0);
nano::fan fan (value0, 1024);
for (auto & i : fan.values)
{
ASSERT_NE (value0, *i);
}
nano::raw_key value1;
fan.value (value1);
ASSERT_EQ (value0, value1);
}
TEST (fan, change)
{
nano::raw_key value0;
value0 = 0;
nano::raw_key value1;
value1 = 1;
ASSERT_NE (value0, value1);
nano::fan fan (value0, 1024);
ASSERT_EQ (1024, fan.values.size ());
nano::raw_key value2;
fan.value (value2);
ASSERT_EQ (value0, value2);
fan.value_set (value1);
fan.value (value2);
ASSERT_EQ (value1, value2);
}
TEST (wallet, reopen_default_password)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
auto transaction (env.tx_begin_write ());
ASSERT_FALSE (init);
nano::kdf kdf{ nano::dev::network_params.kdf_work };
{
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
ASSERT_TRUE (wallet.valid_password (transaction));
}
{
bool init;
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
ASSERT_TRUE (wallet.valid_password (transaction));
}
{
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
wallet.rekey (transaction, "");
ASSERT_TRUE (wallet.valid_password (transaction));
}
{
bool init;
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (init);
ASSERT_FALSE (wallet.valid_password (transaction));
wallet.attempt_password (transaction, " ");
ASSERT_FALSE (wallet.valid_password (transaction));
wallet.attempt_password (transaction, "");
ASSERT_TRUE (wallet.valid_password (transaction));
}
}
TEST (wallet, representative)
{
auto error (false);
nano::mdb_env env (error, nano::unique_path ());
ASSERT_FALSE (error);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (error, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (error);
ASSERT_FALSE (wallet.is_representative (transaction));
ASSERT_EQ (nano::dev::genesis->account (), wallet.representative (transaction));
ASSERT_FALSE (wallet.is_representative (transaction));
nano::keypair key;
wallet.representative_set (transaction, key.pub);
ASSERT_FALSE (wallet.is_representative (transaction));
ASSERT_EQ (key.pub, wallet.representative (transaction));
ASSERT_FALSE (wallet.is_representative (transaction));
wallet.insert_adhoc (transaction, key.prv);
ASSERT_TRUE (wallet.is_representative (transaction));
}
TEST (wallet, serialize_json_empty)
{
auto error (false);
nano::mdb_env env (error, nano::unique_path ());
ASSERT_FALSE (error);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet1 (error, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (error);
std::string serialized;
wallet1.serialize_json (transaction, serialized);
nano::wallet_store wallet2 (error, kdf, transaction, nano::dev::genesis->account (), 1, "1", serialized);
ASSERT_FALSE (error);
nano::raw_key password1;
nano::raw_key password2;
wallet1.wallet_key (password1, transaction);
wallet2.wallet_key (password2, transaction);
ASSERT_EQ (password1, password2);
ASSERT_EQ (wallet1.salt (transaction), wallet2.salt (transaction));
ASSERT_EQ (wallet1.check (transaction), wallet2.check (transaction));
ASSERT_EQ (wallet1.representative (transaction), wallet2.representative (transaction));
ASSERT_EQ (wallet1.end (), wallet1.begin (transaction));
ASSERT_EQ (wallet2.end (), wallet2.begin (transaction));
}
TEST (wallet, serialize_json_one)
{
auto error (false);
nano::mdb_env env (error, nano::unique_path ());
ASSERT_FALSE (error);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet1 (error, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (error);
nano::keypair key;
wallet1.insert_adhoc (transaction, key.prv);
std::string serialized;
wallet1.serialize_json (transaction, serialized);
nano::wallet_store wallet2 (error, kdf, transaction, nano::dev::genesis->account (), 1, "1", serialized);
ASSERT_FALSE (error);
nano::raw_key password1;
nano::raw_key password2;
wallet1.wallet_key (password1, transaction);
wallet2.wallet_key (password2, transaction);
ASSERT_EQ (password1, password2);
ASSERT_EQ (wallet1.salt (transaction), wallet2.salt (transaction));
ASSERT_EQ (wallet1.check (transaction), wallet2.check (transaction));
ASSERT_EQ (wallet1.representative (transaction), wallet2.representative (transaction));
ASSERT_TRUE (wallet2.exists (transaction, key.pub));
nano::raw_key prv;
wallet2.fetch (transaction, key.pub, prv);
ASSERT_EQ (key.prv, prv);
}
TEST (wallet, serialize_json_password)
{
auto error (false);
nano::mdb_env env (error, nano::unique_path ());
ASSERT_FALSE (error);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet1 (error, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (error);
nano::keypair key;
wallet1.rekey (transaction, "password");
wallet1.insert_adhoc (transaction, key.prv);
std::string serialized;
wallet1.serialize_json (transaction, serialized);
nano::wallet_store wallet2 (error, kdf, transaction, nano::dev::genesis->account (), 1, "1", serialized);
ASSERT_FALSE (error);
ASSERT_FALSE (wallet2.valid_password (transaction));
ASSERT_FALSE (wallet2.attempt_password (transaction, "password"));
ASSERT_TRUE (wallet2.valid_password (transaction));
nano::raw_key password1;
nano::raw_key password2;
wallet1.wallet_key (password1, transaction);
wallet2.wallet_key (password2, transaction);
ASSERT_EQ (password1, password2);
ASSERT_EQ (wallet1.salt (transaction), wallet2.salt (transaction));
ASSERT_EQ (wallet1.check (transaction), wallet2.check (transaction));
ASSERT_EQ (wallet1.representative (transaction), wallet2.representative (transaction));
ASSERT_TRUE (wallet2.exists (transaction, key.pub));
nano::raw_key prv;
wallet2.fetch (transaction, key.pub, prv);
ASSERT_EQ (key.prv, prv);
}
TEST (wallet_store, move)
{
auto error (false);
nano::mdb_env env (error, nano::unique_path ());
ASSERT_FALSE (error);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet1 (error, kdf, transaction, nano::dev::genesis->account (), 1, "0");
ASSERT_FALSE (error);
nano::keypair key1;
wallet1.insert_adhoc (transaction, key1.prv);
nano::wallet_store wallet2 (error, kdf, transaction, nano::dev::genesis->account (), 1, "1");
ASSERT_FALSE (error);
nano::keypair key2;
wallet2.insert_adhoc (transaction, key2.prv);
ASSERT_FALSE (wallet1.exists (transaction, key2.pub));
ASSERT_TRUE (wallet2.exists (transaction, key2.pub));
std::vector<nano::public_key> keys;
keys.push_back (key2.pub);
ASSERT_FALSE (wallet1.move (transaction, wallet2, keys));
ASSERT_TRUE (wallet1.exists (transaction, key2.pub));
ASSERT_FALSE (wallet2.exists (transaction, key2.pub));
}
TEST (wallet_store, import)
{
nano::test::system system (2);
auto wallet1 (system.wallet (0));
auto wallet2 (system.wallet (1));
nano::keypair key1;
wallet1->insert_adhoc (key1.prv);
std::string json;
wallet1->serialize (json);
ASSERT_FALSE (wallet2->exists (key1.pub));
auto error (wallet2->import (json, ""));
ASSERT_FALSE (error);
ASSERT_TRUE (wallet2->exists (key1.pub));
}
TEST (wallet_store, fail_import_bad_password)
{
nano::test::system system (2);
auto wallet1 (system.wallet (0));
auto wallet2 (system.wallet (1));
nano::keypair key1;
wallet1->insert_adhoc (key1.prv);
std::string json;
wallet1->serialize (json);
ASSERT_FALSE (wallet2->exists (key1.pub));
auto error (wallet2->import (json, "1"));
ASSERT_TRUE (error);
}
TEST (wallet_store, fail_import_corrupt)
{
nano::test::system system (2);
auto wallet1 (system.wallet (1));
std::string json;
auto error (wallet1->import (json, "1"));
ASSERT_TRUE (error);
}
// Test work is precached when a key is inserted
TEST (wallet, work)
{
nano::test::system system (1);
auto wallet (system.wallet (0));
wallet->insert_adhoc (nano::dev::genesis_key.prv);
wallet->insert_adhoc (nano::dev::genesis_key.prv);
auto done (false);
system.deadline_set (20s);
while (!done)
{
auto transaction (system.wallet (0)->wallets.tx_begin_read ());
uint64_t work (0);
if (!wallet->store.work_get (transaction, nano::dev::genesis_key.pub, work))
{
done = nano::dev::network_params.work.difficulty (nano::dev::genesis->work_version (), nano::dev::genesis->hash (), work) >= system.nodes[0]->default_difficulty (nano::dev::genesis->work_version ());
}
ASSERT_NO_ERROR (system.poll ());
}
}
TEST (wallet, work_generate)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
auto wallet (system.wallet (0));
nano::uint128_t amount1 (node1.balance (nano::dev::genesis_key.pub));
uint64_t work1;
wallet->insert_adhoc (nano::dev::genesis_key.prv);
nano::account account1;
{
auto transaction (node1.wallets.tx_begin_read ());
account1 = system.account (transaction, 0);
}
nano::keypair key;
auto block (wallet->send_action (nano::dev::genesis_key.pub, key.pub, 100));
auto transaction (node1.store.tx_begin_read ());
ASSERT_TIMELY (10s, node1.ledger.account_balance (transaction, nano::dev::genesis_key.pub) != amount1);
system.deadline_set (10s);
auto again (true);
while (again)
{
ASSERT_NO_ERROR (system.poll ());
auto block_transaction (node1.store.tx_begin_read ());
auto transaction (system.wallet (0)->wallets.tx_begin_read ());
again = wallet->store.work_get (transaction, account1, work1) || nano::dev::network_params.work.difficulty (block->work_version (), node1.ledger.latest_root (block_transaction, account1), work1) < node1.default_difficulty (block->work_version ());
}
}
TEST (wallet, work_cache_delayed)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
auto wallet (system.wallet (0));
uint64_t work1;
wallet->insert_adhoc (nano::dev::genesis_key.prv);
nano::account account1;
{
auto transaction (node1.wallets.tx_begin_read ());
account1 = system.account (transaction, 0);
}
nano::keypair key;
auto block1 (wallet->send_action (nano::dev::genesis_key.pub, key.pub, 100));
ASSERT_EQ (block1->hash (), node1.latest (nano::dev::genesis_key.pub));
auto block2 (wallet->send_action (nano::dev::genesis_key.pub, key.pub, 100));
ASSERT_EQ (block2->hash (), node1.latest (nano::dev::genesis_key.pub));
ASSERT_EQ (block2->hash (), node1.wallets.delayed_work->operator[] (nano::dev::genesis_key.pub).as_block_hash ());
auto threshold (node1.default_difficulty (nano::work_version::work_1));
auto again (true);
system.deadline_set (10s);
while (again)
{
ASSERT_NO_ERROR (system.poll ());
if (!wallet->store.work_get (node1.wallets.tx_begin_read (), account1, work1))
{
again = nano::dev::network_params.work.difficulty (nano::work_version::work_1, block2->hash (), work1) < threshold;
}
}
ASSERT_GE (nano::dev::network_params.work.difficulty (nano::work_version::work_1, block2->hash (), work1), threshold);
}
TEST (wallet, insert_locked)
{
nano::test::system system (1);
auto wallet (system.wallet (0));
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->store.rekey (transaction, "1");
ASSERT_TRUE (wallet->store.valid_password (transaction));
wallet->enter_password (transaction, "");
}
auto transaction (wallet->wallets.tx_begin_read ());
ASSERT_FALSE (wallet->store.valid_password (transaction));
ASSERT_TRUE (wallet->insert_adhoc (nano::keypair ().prv).is_zero ());
}
TEST (wallet, deterministic_keys)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
auto key1 = wallet.deterministic_key (transaction, 0);
auto key2 = wallet.deterministic_key (transaction, 0);
ASSERT_EQ (key1, key2);
auto key3 = wallet.deterministic_key (transaction, 1);
ASSERT_NE (key1, key3);
ASSERT_EQ (0, wallet.deterministic_index_get (transaction));
wallet.deterministic_index_set (transaction, 1);
ASSERT_EQ (1, wallet.deterministic_index_get (transaction));
auto key4 (wallet.deterministic_insert (transaction));
nano::raw_key key5;
ASSERT_FALSE (wallet.fetch (transaction, key4, key5));
ASSERT_EQ (key3, key5);
ASSERT_EQ (2, wallet.deterministic_index_get (transaction));
wallet.deterministic_index_set (transaction, 1);
ASSERT_EQ (1, wallet.deterministic_index_get (transaction));
wallet.erase (transaction, key4);
ASSERT_FALSE (wallet.exists (transaction, key4));
auto key8 (wallet.deterministic_insert (transaction));
ASSERT_EQ (key4, key8);
auto key6 (wallet.deterministic_insert (transaction));
nano::raw_key key7;
ASSERT_FALSE (wallet.fetch (transaction, key6, key7));
ASSERT_NE (key5, key7);
ASSERT_EQ (3, wallet.deterministic_index_get (transaction));
nano::keypair key9;
ASSERT_EQ (key9.pub, wallet.insert_adhoc (transaction, key9.prv));
ASSERT_TRUE (wallet.exists (transaction, key9.pub));
wallet.deterministic_clear (transaction);
ASSERT_EQ (0, wallet.deterministic_index_get (transaction));
ASSERT_FALSE (wallet.exists (transaction, key4));
ASSERT_FALSE (wallet.exists (transaction, key6));
ASSERT_FALSE (wallet.exists (transaction, key8));
ASSERT_TRUE (wallet.exists (transaction, key9.pub));
}
TEST (wallet, reseed)
{
bool init;
nano::mdb_env env (init, nano::unique_path ());
ASSERT_FALSE (init);
auto transaction (env.tx_begin_write ());
nano::kdf kdf{ nano::dev::network_params.kdf_work };
nano::wallet_store wallet (init, kdf, transaction, nano::dev::genesis->account (), 1, "0");
nano::raw_key seed1;
seed1 = 1;
nano::raw_key seed2;
seed2 = 2;
wallet.seed_set (transaction, seed1);
nano::raw_key seed3;
wallet.seed (seed3, transaction);
ASSERT_EQ (seed1, seed3);
auto key1 (wallet.deterministic_insert (transaction));
ASSERT_EQ (1, wallet.deterministic_index_get (transaction));
wallet.seed_set (transaction, seed2);
ASSERT_EQ (0, wallet.deterministic_index_get (transaction));
nano::raw_key seed4;
wallet.seed (seed4, transaction);
ASSERT_EQ (seed2, seed4);
auto key2 (wallet.deterministic_insert (transaction));
ASSERT_NE (key1, key2);
wallet.seed_set (transaction, seed1);
nano::raw_key seed5;
wallet.seed (seed5, transaction);
ASSERT_EQ (seed1, seed5);
auto key3 (wallet.deterministic_insert (transaction));
ASSERT_EQ (key1, key3);
}
TEST (wallet, insert_deterministic_locked)
{
nano::test::system system (1);
auto wallet (system.wallet (0));
auto transaction (wallet->wallets.tx_begin_write ());
wallet->store.rekey (transaction, "1");
ASSERT_TRUE (wallet->store.valid_password (transaction));
wallet->enter_password (transaction, "");
ASSERT_FALSE (wallet->store.valid_password (transaction));
ASSERT_TRUE (wallet->deterministic_insert (transaction).is_zero ());
}
TEST (wallet, no_work)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv, false);
nano::keypair key2;
auto block (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, std::numeric_limits<nano::uint128_t>::max (), false));
ASSERT_NE (nullptr, block);
ASSERT_NE (0, block->block_work ());
ASSERT_GE (nano::dev::network_params.work.difficulty (*block), nano::dev::network_params.work.threshold (block->work_version (), block->sideband ().details));
auto transaction (system.wallet (0)->wallets.tx_begin_read ());
uint64_t cached_work (0);
system.wallet (0)->store.work_get (transaction, nano::dev::genesis_key.pub, cached_work);
ASSERT_EQ (0, cached_work);
}
TEST (wallet, send_race)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2;
for (auto i (1); i < 60; ++i)
{
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, nano::Gxrb_ratio));
ASSERT_EQ (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * i, system.nodes[0]->balance (nano::dev::genesis_key.pub));
}
}
TEST (wallet, password_race)
{
nano::test::system system (1);
nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads);
auto wallet = system.wallet (0);
std::thread thread ([&wallet] () {
for (int i = 0; i < 100; i++)
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->store.rekey (transaction, std::to_string (i));
}
});
for (int i = 0; i < 100; i++)
{
auto transaction (wallet->wallets.tx_begin_read ());
// Password should always be valid, the rekey operation should be atomic.
bool ok = wallet->store.valid_password (transaction);
EXPECT_TRUE (ok);
if (!ok)
{
break;
}
}
thread.join ();
system.stop ();
runner.join ();
}
TEST (wallet, password_race_corrupt_seed)
{
nano::test::system system (1);
nano::thread_runner runner (system.io_ctx, system.nodes[0]->config.io_threads);
auto wallet = system.wallet (0);
nano::raw_key seed;
{
auto transaction (wallet->wallets.tx_begin_write ());
ASSERT_FALSE (wallet->store.rekey (transaction, "4567"));
wallet->store.seed (seed, transaction);
ASSERT_FALSE (wallet->store.attempt_password (transaction, "4567"));
}
std::vector<std::thread> threads;
for (int i = 0; i < 100; i++)
{
threads.emplace_back ([&wallet] () {
for (int i = 0; i < 10; i++)
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->store.rekey (transaction, "0000");
}
});
threads.emplace_back ([&wallet] () {
for (int i = 0; i < 10; i++)
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->store.rekey (transaction, "1234");
}
});
threads.emplace_back ([&wallet] () {
for (int i = 0; i < 10; i++)
{
auto transaction (wallet->wallets.tx_begin_read ());
wallet->store.attempt_password (transaction, "1234");
}
});
}
for (auto & thread : threads)
{
thread.join ();
}
system.stop ();
runner.join ();
{
auto transaction (wallet->wallets.tx_begin_write ());
if (!wallet->store.attempt_password (transaction, "1234"))
{
nano::raw_key seed_now;
wallet->store.seed (seed_now, transaction);
ASSERT_TRUE (seed_now == seed);
}
else if (!wallet->store.attempt_password (transaction, "0000"))
{
nano::raw_key seed_now;
wallet->store.seed (seed_now, transaction);
ASSERT_TRUE (seed_now == seed);
}
else if (!wallet->store.attempt_password (transaction, "4567"))
{
nano::raw_key seed_now;
wallet->store.seed (seed_now, transaction);
ASSERT_TRUE (seed_now == seed);
}
else
{
ASSERT_FALSE (true);
}
}
}
TEST (wallet, change_seed)
{
nano::test::system system (1);
auto wallet (system.wallet (0));
wallet->enter_initial_password ();
nano::raw_key seed1;
seed1 = 1;
nano::public_key pub;
uint32_t index (4);
auto prv = nano::deterministic_key (seed1, index);
pub = nano::pub_key (prv);
wallet->insert_adhoc (nano::dev::genesis_key.prv, false);
auto block (wallet->send_action (nano::dev::genesis_key.pub, pub, 100));
ASSERT_NE (nullptr, block);
system.nodes[0]->block_processor.flush ();
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->change_seed (transaction, seed1);
nano::raw_key seed2;
wallet->store.seed (seed2, transaction);
ASSERT_EQ (seed1, seed2);
ASSERT_EQ (index + 1, wallet->store.deterministic_index_get (transaction));
}
ASSERT_TRUE (wallet->exists (pub));
}
TEST (wallet, deterministic_restore)
{
nano::test::system system (1);
auto wallet (system.wallet (0));
wallet->enter_initial_password ();
nano::raw_key seed1;
seed1 = 1;
nano::public_key pub;
uint32_t index (4);
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->change_seed (transaction, seed1);
nano::raw_key seed2;
wallet->store.seed (seed2, transaction);
ASSERT_EQ (seed1, seed2);
ASSERT_EQ (1, wallet->store.deterministic_index_get (transaction));
auto prv = nano::deterministic_key (seed1, index);
pub = nano::pub_key (prv);
}
wallet->insert_adhoc (nano::dev::genesis_key.prv, false);
auto block (wallet->send_action (nano::dev::genesis_key.pub, pub, 100));
ASSERT_NE (nullptr, block);
system.nodes[0]->block_processor.flush ();
{
auto transaction (wallet->wallets.tx_begin_write ());
wallet->deterministic_restore (transaction);
ASSERT_EQ (index + 1, wallet->store.deterministic_index_get (transaction));
}
ASSERT_TRUE (wallet->exists (pub));
}
TEST (wallet, epoch_2_validation)
{
nano::test::system system (1);
auto & node (*system.nodes[0]);
auto & wallet (*system.wallet (0));
// Upgrade the genesis account to epoch 2
ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_1));
ASSERT_NE (nullptr, system.upgrade_genesis_epoch (node, nano::epoch::epoch_2));
wallet.insert_adhoc (nano::dev::genesis_key.prv, false);
// Test send and receive blocks
// An epoch 2 receive block should be generated with lower difficulty with high probability
auto tries = 0;
auto max_tries = 20;
auto amount = node.config.receive_minimum.number ();
while (++tries < max_tries)
{
auto send = wallet.send_action (nano::dev::genesis_key.pub, nano::dev::genesis_key.pub, amount, 1);
ASSERT_NE (nullptr, send);
ASSERT_EQ (nano::epoch::epoch_2, send->sideband ().details.epoch);
ASSERT_EQ (nano::epoch::epoch_0, send->sideband ().source_epoch); // Not used for send state blocks
auto receive = wallet.receive_action (send->hash (), nano::dev::genesis_key.pub, amount, send->link ().as_account (), 1);
ASSERT_NE (nullptr, receive);
if (nano::dev::network_params.work.difficulty (*receive) < node.network_params.work.base)
{
ASSERT_GE (nano::dev::network_params.work.difficulty (*receive), node.network_params.work.epoch_2_receive);
ASSERT_EQ (nano::epoch::epoch_2, receive->sideband ().details.epoch);
ASSERT_EQ (nano::epoch::epoch_2, receive->sideband ().source_epoch);
break;
}
}
ASSERT_LT (tries, max_tries);
// Test a change block
ASSERT_NE (nullptr, wallet.change_action (nano::dev::genesis_key.pub, nano::keypair ().pub, 1));
}
// Receiving from an upgraded account uses the lower threshold and upgrades the receiving account
TEST (wallet, epoch_2_receive_propagation)
{
auto tries = 0;
auto const max_tries = 20;
while (++tries < max_tries)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.disable_request_loop = true;
auto & node (*system.add_node (node_flags));
auto & wallet (*system.wallet (0));
// Upgrade the genesis account to epoch 1
auto epoch1 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_1);
ASSERT_NE (nullptr, epoch1);
nano::keypair key;
nano::state_block_builder builder;
// Send and open the account
wallet.insert_adhoc (nano::dev::genesis_key.prv, false);
wallet.insert_adhoc (key.prv, false);
auto amount = node.config.receive_minimum.number ();
auto send1 = wallet.send_action (nano::dev::genesis_key.pub, key.pub, amount, 1);
ASSERT_NE (nullptr, send1);
ASSERT_NE (nullptr, wallet.receive_action (send1->hash (), nano::dev::genesis_key.pub, amount, send1->link ().as_account (), 1));
// Upgrade the genesis account to epoch 2
auto epoch2 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_2);
ASSERT_NE (nullptr, epoch2);
// Send a block
auto send2 = wallet.send_action (nano::dev::genesis_key.pub, key.pub, amount, 1);
ASSERT_NE (nullptr, send2);
auto receive2 = wallet.receive_action (send2->hash (), key.pub, amount, send2->link ().as_account (), 1);
ASSERT_NE (nullptr, receive2);
if (nano::dev::network_params.work.difficulty (*receive2) < node.network_params.work.base)
{
ASSERT_GE (nano::dev::network_params.work.difficulty (*receive2), node.network_params.work.epoch_2_receive);
ASSERT_EQ (nano::epoch::epoch_2, node.store.block.version (node.store.tx_begin_read (), receive2->hash ()));
ASSERT_EQ (nano::epoch::epoch_2, receive2->sideband ().source_epoch);
break;
}
}
ASSERT_LT (tries, max_tries);
}
// Opening an upgraded account uses the lower threshold
TEST (wallet, epoch_2_receive_unopened)
{
// Ensure the lower receive work is used when receiving
auto tries = 0;
auto const max_tries = 20;
while (++tries < max_tries)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.disable_request_loop = true;
auto & node (*system.add_node (node_flags));
auto & wallet (*system.wallet (0));
// Upgrade the genesis account to epoch 1
auto epoch1 = system.upgrade_genesis_epoch (node, nano::epoch::epoch_1);
ASSERT_NE (nullptr, epoch1);
nano::keypair key;
nano::state_block_builder builder;
// Send
wallet.insert_adhoc (nano::dev::genesis_key.prv, false);
auto amount = node.config.receive_minimum.number ();
auto send1 = wallet.send_action (nano::dev::genesis_key.pub, key.pub, amount, 1);
// Upgrade unopened account to epoch_2
auto epoch2_unopened = builder
.account (key.pub)
.previous (0)
.representative (0)
.balance (0)
.link (node.network_params.ledger.epochs.link (nano::epoch::epoch_2))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (key.pub, node.network_params.work.epoch_2))
.build ();
ASSERT_EQ (nano::process_result::progress, node.process (*epoch2_unopened).code);
wallet.insert_adhoc (key.prv, false);
auto receive1 = wallet.receive_action (send1->hash (), key.pub, amount, send1->link ().as_account (), 1);
ASSERT_NE (nullptr, receive1);
if (nano::dev::network_params.work.difficulty (*receive1) < node.network_params.work.base)
{
ASSERT_GE (nano::dev::network_params.work.difficulty (*receive1), node.network_params.work.epoch_2_receive);
ASSERT_EQ (nano::epoch::epoch_2, node.store.block.version (node.store.tx_begin_read (), receive1->hash ()));
ASSERT_EQ (nano::epoch::epoch_1, receive1->sideband ().source_epoch);
break;
}
}
ASSERT_LT (tries, max_tries);
}
/**
* This test checks that wallets::foreach_representative can be used recursively
*/
TEST (wallet, foreach_representative_deadlock)
{
nano::test::system system (1);
auto & node (*system.nodes[0]);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
node.wallets.compute_reps ();
ASSERT_EQ (1, node.wallets.reps ().voting);
bool set = false;
node.wallets.foreach_representative ([&node, &set, &system] (nano::public_key const & pub, nano::raw_key const & prv) {
node.wallets.foreach_representative ([&node, &set, &system] (nano::public_key const & pub, nano::raw_key const & prv) {
ASSERT_TIMELY (5s, node.wallets.mutex.try_lock () == 1);
node.wallets.mutex.unlock ();
set = true;
});
});
ASSERT_TRUE (set);
}
TEST (wallet, search_receivable)
{
nano::test::system system;
nano::node_config config = system.default_config ();
config.enable_voting = false;
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
nano::node_flags flags;
flags.disable_search_pending = true;
auto & node (*system.add_node (config, flags));
auto & wallet (*system.wallet (0));
wallet.insert_adhoc (nano::dev::genesis_key.prv);
nano::block_builder builder;
auto send = builder.state ()
.account (nano::dev::genesis->account ())
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - node.config.receive_minimum.number ())
.link (nano::dev::genesis->account ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::process_result::progress, node.process (*send).code);
// Pending search should start an election
ASSERT_TRUE (node.active.empty ());
ASSERT_FALSE (wallet.search_receivable (wallet.wallets.tx_begin_read ()));
auto election = node.active.election (send->qualified_root ());
ASSERT_NE (nullptr, election);
// Erase the key so the confirmation does not trigger an automatic receive
wallet.store.erase (node.wallets.tx_begin_write (), nano::dev::genesis->account ());
// Now confirm the election
election->force_confirm ();
ASSERT_TIMELY (5s, node.block_confirmed (send->hash ()) && node.active.empty ());
// Re-insert the key
wallet.insert_adhoc (nano::dev::genesis_key.prv);
// Pending search should create the receive block
ASSERT_EQ (2, node.ledger.cache.block_count);
ASSERT_FALSE (wallet.search_receivable (wallet.wallets.tx_begin_read ()));
ASSERT_TIMELY (3s, node.balance (nano::dev::genesis->account ()) == nano::dev::constants.genesis_amount);
auto receive_hash = node.ledger.latest (node.store.tx_begin_read (), nano::dev::genesis->account ());
auto receive = node.block (receive_hash);
ASSERT_NE (nullptr, receive);
ASSERT_EQ (receive->sideband ().height, 3);
ASSERT_EQ (send->hash (), receive->link ().as_block_hash ());
}
TEST (wallet, receive_pruned)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.disable_request_loop = true;
auto & node1 = *system.add_node (node_flags);
node_flags.enable_pruning = true;
nano::node_config config = system.default_config ();
config.enable_voting = false; // Remove after allowing pruned voting
auto & node2 = *system.add_node (config, node_flags);
auto & wallet1 = *system.wallet (0);
auto & wallet2 = *system.wallet (1);
nano::keypair key;
nano::state_block_builder builder;
// Send
wallet1.insert_adhoc (nano::dev::genesis_key.prv, false);
auto amount = node2.config.receive_minimum.number ();
auto send1 = wallet1.send_action (nano::dev::genesis_key.pub, key.pub, amount, 1);
auto send2 = wallet1.send_action (nano::dev::genesis_key.pub, key.pub, 1, 1);
// Pruning
ASSERT_TIMELY (5s, node2.ledger.cache.cemented_count == 3);
{
auto transaction = node2.store.tx_begin_write ();
ASSERT_EQ (1, node2.ledger.pruning_action (transaction, send1->hash (), 2));
}
ASSERT_EQ (1, node2.ledger.cache.pruned_count);
ASSERT_TRUE (node2.ledger.block_or_pruned_exists (send1->hash ()));
ASSERT_FALSE (node2.store.block.exists (node2.store.tx_begin_read (), send1->hash ()));
wallet2.insert_adhoc (key.prv, false);
auto open1 = wallet2.receive_action (send1->hash (), key.pub, amount, send1->link ().as_account (), 1);
ASSERT_NE (nullptr, open1);
ASSERT_EQ (amount, node2.ledger.balance (node2.store.tx_begin_read (), open1->hash ()));
ASSERT_TIMELY (5s, node2.ledger.cache.cemented_count == 4);
}