text: #include <vban/crypto_lib/random_pool.hpp>
  #include <vban/lib/threading.hpp>
  #include <vban/lib/utility.hpp>
  #include <vban/node/election.hpp>
  #include <vban/node/lmdb/lmdb_iterator.hpp>
  #include <vban/node/node.hpp>
  #include <vban/node/wallet.hpp>
  
  #include <boost/filesystem.hpp>
  #include <boost/format.hpp>
  #include <boost/polymorphic_cast.hpp>
  #include <boost/property_tree/json_parser.hpp>
  
  #include <future>
  
  #include <argon2.h>
  
  vban::uint256_union vban::wallet_store::check (vban::transaction const & transaction_a)
  {
  	vban::wallet_value value (entry_get_raw (transaction_a, vban::wallet_store::check_special));
  	return value.key;
  }
  
  vban::uint256_union vban::wallet_store::salt (vban::transaction const & transaction_a)
  {
  	vban::wallet_value value (entry_get_raw (transaction_a, vban::wallet_store::salt_special));
  	return value.key;
  }
  
  void vban::wallet_store::wallet_key (vban::raw_key & prv_a, vban::transaction const & transaction_a)
  {
  	vban::lock_guard<std::recursive_mutex> lock (mutex);
  	vban::raw_key wallet_l;
  	wallet_key_mem.value (wallet_l);
  	vban::raw_key password_l;
  	password.value (password_l);
  	prv_a.decrypt (wallet_l, password_l, salt (transaction_a).owords[0]);
  }
  
  void vban::wallet_store::seed (vban::raw_key & prv_a, vban::transaction const & transaction_a)
  {
  	vban::wallet_value value (entry_get_raw (transaction_a, vban::wallet_store::seed_special));
  	vban::raw_key password_l;
  	wallet_key (password_l, transaction_a);
  	prv_a.decrypt (value.key, password_l, salt (transaction_a).owords[seed_iv_index]);
  }
  
  void vban::wallet_store::seed_set (vban::transaction const & transaction_a, vban::raw_key const & prv_a)
  {
  	vban::raw_key password_l;
  	wallet_key (password_l, transaction_a);
  	vban::raw_key ciphertext;
  	ciphertext.encrypt (prv_a, password_l, salt (transaction_a).owords[seed_iv_index]);
  	entry_put_raw (transaction_a, vban::wallet_store::seed_special, vban::wallet_value (ciphertext, 0));
  	deterministic_clear (transaction_a);
  }
  
  vban::public_key vban::wallet_store::deterministic_insert (vban::transaction const & transaction_a)
  {
  	auto index (deterministic_index_get (transaction_a));
  	auto prv = deterministic_key (transaction_a, index);
  	vban::public_key result (vban::pub_key (prv));
  	while (exists (transaction_a, result))
  	{
  		++index;
  		prv = deterministic_key (transaction_a, index);
  		result = vban::pub_key (prv);
  	}
  	uint64_t marker (1);
  	marker <<= 32;
  	marker |= index;
  	entry_put_raw (transaction_a, result, vban::wallet_value (marker, 0));
  	++index;
  	deterministic_index_set (transaction_a, index);
  	return result;
  }
  
  vban::public_key vban::wallet_store::deterministic_insert (vban::transaction const & transaction_a, uint32_t const index)
  {
  	auto prv = deterministic_key (transaction_a, index);
  	vban::public_key result (vban::pub_key (prv));
  	uint64_t marker (1);
  	marker <<= 32;
  	marker |= index;
  	entry_put_raw (transaction_a, result, vban::wallet_value (marker, 0));
  	return result;
  }
  
  vban::raw_key vban::wallet_store::deterministic_key (vban::transaction const & transaction_a, uint32_t index_a)
  {
  	debug_assert (valid_password (transaction_a));
  	vban::raw_key seed_l;
  	seed (seed_l, transaction_a);
  	return vban::deterministic_key (seed_l, index_a);
  }
  
  uint32_t vban::wallet_store::deterministic_index_get (vban::transaction const & transaction_a)
  {
  	vban::wallet_value value (entry_get_raw (transaction_a, vban::wallet_store::deterministic_index_special));
  	return static_cast<uint32_t> (value.key.number () & static_cast<uint32_t> (-1));
  }
  
  void vban::wallet_store::deterministic_index_set (vban::transaction const & transaction_a, uint32_t index_a)
  {
  	vban::raw_key index_l (index_a);
  	vban::wallet_value value (index_l, 0);
  	entry_put_raw (transaction_a, vban::wallet_store::deterministic_index_special, value);
  }
  
  void vban::wallet_store::deterministic_clear (vban::transaction const & transaction_a)
  {
  	vban::uint256_union key (0);
  	for (auto i (begin (transaction_a)), n (end ()); i != n;)
  	{
  		switch (key_type (vban::wallet_value (i->second)))
  		{
  			case vban::key_type::deterministic:
  			{
  				auto const & key (i->first);
  				erase (transaction_a, key);
  				i = begin (transaction_a, key);
  				break;
  			}
  			default:
  			{
  				++i;
  				break;
  			}
  		}
  	}
  	deterministic_index_set (transaction_a, 0);
  }
  
  bool vban::wallet_store::valid_password (vban::transaction const & transaction_a)
  {
  	vban::raw_key zero;
  	zero.clear ();
  	vban::raw_key wallet_key_l;
  	wallet_key (wallet_key_l, transaction_a);
  	vban::uint256_union check_l;
  	check_l.encrypt (zero, wallet_key_l, salt (transaction_a).owords[check_iv_index]);
  	bool ok = check (transaction_a) == check_l;
  	return ok;
  }
  
  bool vban::wallet_store::attempt_password (vban::transaction const & transaction_a, std::string const & password_a)
  {
  	bool result = false;
  	{
  		vban::lock_guard<std::recursive_mutex> lock (mutex);
  		vban::raw_key password_l;
  		derive_key (password_l, transaction_a, password_a);
  		password.value_set (password_l);
  		result = !valid_password (transaction_a);
  	}
  	if (!result)
  	{
  		switch (version (transaction_a))
  		{
  			case version_4:
  				break;
  			default:
  				debug_assert (false);
  		}
  	}
  	return result;
  }
  
  bool vban::wallet_store::rekey (vban::transaction const & transaction_a, std::string const & password_a)
  {
  	vban::lock_guard<std::recursive_mutex> lock (mutex);
  	bool result (false);
  	if (valid_password (transaction_a))
  	{
  		vban::raw_key password_new;
  		derive_key (password_new, transaction_a, password_a);
  		vban::raw_key wallet_key_l;
  		wallet_key (wallet_key_l, transaction_a);
  		vban::raw_key password_l;
  		password.value (password_l);
  		password.value_set (password_new);
  		vban::raw_key encrypted;
  		encrypted.encrypt (wallet_key_l, password_new, salt (transaction_a).owords[0]);
  		vban::raw_key wallet_enc;
  		wallet_enc = encrypted;
  		wallet_key_mem.value_set (wallet_enc);
  		entry_put_raw (transaction_a, vban::wallet_store::wallet_key_special, vban::wallet_value (encrypted, 0));
  	}
  	else
  	{
  		result = true;
  	}
  	return result;
  }
  
  void vban::wallet_store::derive_key (vban::raw_key & prv_a, vban::transaction const & transaction_a, std::string const & password_a)
  {
  	auto salt_l (salt (transaction_a));
  	kdf.phs (prv_a, password_a, salt_l);
  }
  
  vban::fan::fan (vban::raw_key const & key, size_t count_a)
  {
  	auto first (std::make_unique<vban::raw_key> (key));
  	for (auto i (1); i < count_a; ++i)
  	{
  		auto entry (std::make_unique<vban::raw_key> ());
  		vban::random_pool::generate_block (entry->bytes.data (), entry->bytes.size ());
  		*first ^= *entry;
  		values.push_back (std::move (entry));
  	}
  	values.push_back (std::move (first));
  }
  
  void vban::fan::value (vban::raw_key & prv_a)
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	value_get (prv_a);
  }
  
  void vban::fan::value_get (vban::raw_key & prv_a)
  {
  	debug_assert (!mutex.try_lock ());
  	prv_a.clear ();
  	for (auto & i : values)
  	{
  		prv_a ^= *i;
  	}
  }
  
  void vban::fan::value_set (vban::raw_key const & value_a)
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	vban::raw_key value_l;
  	value_get (value_l);
  	*(values[0]) ^= value_l;
  	*(values[0]) ^= value_a;
  }
  
  // Wallet version number
  vban::account const vban::wallet_store::version_special (0);
  // Random number used to salt private key encryption
  vban::account const vban::wallet_store::salt_special (1);
  // Key used to encrypt wallet keys, encrypted itself by the user password
  vban::account const vban::wallet_store::wallet_key_special (2);
  // Check value used to see if password is valid
  vban::account const vban::wallet_store::check_special (3);
  // Representative account to be used if we open a new account
  vban::account const vban::wallet_store::representative_special (4);
  // Wallet seed for deterministic key generation
  vban::account const vban::wallet_store::seed_special (5);
  // Current key index for deterministic keys
  vban::account const vban::wallet_store::deterministic_index_special (6);
  int const vban::wallet_store::special_count (7);
  size_t const vban::wallet_store::check_iv_index (0);
  size_t const vban::wallet_store::seed_iv_index (1);
  
  vban::wallet_store::wallet_store (bool & init_a, vban::kdf & kdf_a, vban::transaction & transaction_a, vban::account representative_a, unsigned fanout_a, std::string const & wallet_a, std::string const & json_a) :
  	password (0, fanout_a),
  	wallet_key_mem (0, fanout_a),
  	kdf (kdf_a)
  {
  	init_a = false;
  	initialize (transaction_a, init_a, wallet_a);
  	if (!init_a)
  	{
  		MDB_val junk;
  		debug_assert (mdb_get (tx (transaction_a), handle, vban::mdb_val (version_special), &junk) == MDB_NOTFOUND);
  		boost::property_tree::ptree wallet_l;
  		std::stringstream istream (json_a);
  		try
  		{
  			boost::property_tree::read_json (istream, wallet_l);
  		}
  		catch (...)
  		{
  			init_a = true;
  		}
  		for (auto i (wallet_l.begin ()), n (wallet_l.end ()); i != n; ++i)
  		{
  			vban::account key;
  			init_a = key.decode_hex (i->first);
  			if (!init_a)
  			{
  				vban::raw_key value;
  				init_a = value.decode_hex (wallet_l.get<std::string> (i->first));
  				if (!init_a)
  				{
  					entry_put_raw (transaction_a, key, vban::wallet_value (value, 0));
  				}
  				else
  				{
  					init_a = true;
  				}
  			}
  			else
  			{
  				init_a = true;
  			}
  		}
  		init_a |= mdb_get (tx (transaction_a), handle, vban::mdb_val (version_special), &junk) != 0;
  		init_a |= mdb_get (tx (transaction_a), handle, vban::mdb_val (wallet_key_special), &junk) != 0;
  		init_a |= mdb_get (tx (transaction_a), handle, vban::mdb_val (salt_special), &junk) != 0;
  		init_a |= mdb_get (tx (transaction_a), handle, vban::mdb_val (check_special), &junk) != 0;
  		init_a |= mdb_get (tx (transaction_a), handle, vban::mdb_val (representative_special), &junk) != 0;
  		vban::raw_key key;
  		key.clear ();
  		password.value_set (key);
  		key = entry_get_raw (transaction_a, vban::wallet_store::wallet_key_special).key;
  		wallet_key_mem.value_set (key);
  	}
  }
  
  vban::wallet_store::wallet_store (bool & init_a, vban::kdf & kdf_a, vban::transaction & transaction_a, vban::account representative_a, unsigned fanout_a, std::string const & wallet_a) :
  	password (0, fanout_a),
  	wallet_key_mem (0, fanout_a),
  	kdf (kdf_a)
  {
  	init_a = false;
  	initialize (transaction_a, init_a, wallet_a);
  	if (!init_a)
  	{
  		int version_status;
  		MDB_val version_value;
  		version_status = mdb_get (tx (transaction_a), handle, vban::mdb_val (version_special), &version_value);
  		if (version_status == MDB_NOTFOUND)
  		{
  			version_put (transaction_a, version_current);
  			vban::raw_key salt_l;
  			random_pool::generate_block (salt_l.bytes.data (), salt_l.bytes.size ());
  			entry_put_raw (transaction_a, vban::wallet_store::salt_special, vban::wallet_value (salt_l, 0));
  			// Wallet key is a fixed random key that encrypts all entries
  			vban::raw_key wallet_key;
  			random_pool::generate_block (wallet_key.bytes.data (), sizeof (wallet_key.bytes));
  			vban::raw_key password_l;
  			password_l.clear ();
  			password.value_set (password_l);
  			vban::raw_key zero;
  			zero.clear ();
  			// Wallet key is encrypted by the user's password
  			vban::raw_key encrypted;
  			encrypted.encrypt (wallet_key, zero, salt_l.owords[0]);
  			entry_put_raw (transaction_a, vban::wallet_store::wallet_key_special, vban::wallet_value (encrypted, 0));
  			vban::raw_key wallet_key_enc;
  			wallet_key_enc = encrypted;
  			wallet_key_mem.value_set (wallet_key_enc);
  			vban::raw_key check;
  			check.encrypt (zero, wallet_key, salt_l.owords[check_iv_index]);
  			entry_put_raw (transaction_a, vban::wallet_store::check_special, vban::wallet_value (check, 0));
  			vban::raw_key rep;
  			rep.bytes = representative_a.bytes;
  			entry_put_raw (transaction_a, vban::wallet_store::representative_special, vban::wallet_value (rep, 0));
  			vban::raw_key seed;
  			random_pool::generate_block (seed.bytes.data (), seed.bytes.size ());
  			seed_set (transaction_a, seed);
  			entry_put_raw (transaction_a, vban::wallet_store::deterministic_index_special, vban::wallet_value (0, 0));
  		}
  	}
  	vban::raw_key key;
  	key = entry_get_raw (transaction_a, vban::wallet_store::wallet_key_special).key;
  	wallet_key_mem.value_set (key);
  }
  
  std::vector<vban::account> vban::wallet_store::accounts (vban::transaction const & transaction_a)
  {
  	std::vector<vban::account> result;
  	for (auto i (begin (transaction_a)), n (end ()); i != n; ++i)
  	{
  		vban::account const & account (i->first);
  		result.push_back (account);
  	}
  	return result;
  }
  
  void vban::wallet_store::initialize (vban::transaction const & transaction_a, bool & init_a, std::string const & path_a)
  {
  	debug_assert (strlen (path_a.c_str ()) == path_a.size ());
  	auto error (0);
  	MDB_dbi handle_l;
  	error |= mdb_dbi_open (tx (transaction_a), path_a.c_str (), MDB_CREATE, &handle_l);
  	handle = handle_l;
  	init_a = error != 0;
  }
  
  bool vban::wallet_store::is_representative (vban::transaction const & transaction_a)
  {
  	return exists (transaction_a, representative (transaction_a));
  }
  
  void vban::wallet_store::representative_set (vban::transaction const & transaction_a, vban::account const & representative_a)
  {
  	vban::raw_key rep;
  	rep.bytes = representative_a.bytes;
  	entry_put_raw (transaction_a, vban::wallet_store::representative_special, vban::wallet_value (rep, 0));
  }
  
  vban::account vban::wallet_store::representative (vban::transaction const & transaction_a)
  {
  	vban::wallet_value value (entry_get_raw (transaction_a, vban::wallet_store::representative_special));
  	return reinterpret_cast<vban::account const &> (value.key);
  }
  
  vban::public_key vban::wallet_store::insert_adhoc (vban::transaction const & transaction_a, vban::raw_key const & prv)
  {
  	debug_assert (valid_password (transaction_a));
  	vban::public_key pub (vban::pub_key (prv));
  	vban::raw_key password_l;
  	wallet_key (password_l, transaction_a);
  	vban::raw_key ciphertext;
  	ciphertext.encrypt (prv, password_l, pub.owords[0].number ());
  	entry_put_raw (transaction_a, pub, vban::wallet_value (ciphertext, 0));
  	return pub;
  }
  
  bool vban::wallet_store::insert_watch (vban::transaction const & transaction_a, vban::account const & pub_a)
  {
  	bool error (!valid_public_key (pub_a));
  	if (!error)
  	{
  		entry_put_raw (transaction_a, pub_a, vban::wallet_value (vban::raw_key (0), 0));
  	}
  	return error;
  }
  
  void vban::wallet_store::erase (vban::transaction const & transaction_a, vban::account const & pub)
  {
  	auto status (mdb_del (tx (transaction_a), handle, vban::mdb_val (pub), nullptr));
  	(void)status;
  	debug_assert (status == 0);
  }
  
  vban::wallet_value vban::wallet_store::entry_get_raw (vban::transaction const & transaction_a, vban::account const & pub_a)
  {
  	vban::wallet_value result;
  	vban::mdb_val value;
  	auto status (mdb_get (tx (transaction_a), handle, vban::mdb_val (pub_a), value));
  	if (status == 0)
  	{
  		result = vban::wallet_value (value);
  	}
  	else
  	{
  		result.key.clear ();
  		result.work = 0;
  	}
  	return result;
  }
  
  void vban::wallet_store::entry_put_raw (vban::transaction const & transaction_a, vban::account const & pub_a, vban::wallet_value const & entry_a)
  {
  	auto status (mdb_put (tx (transaction_a), handle, vban::mdb_val (pub_a), vban::mdb_val (sizeof (entry_a), const_cast<vban::wallet_value *> (&entry_a)), 0));
  	(void)status;
  	debug_assert (status == 0);
  }
  
  vban::key_type vban::wallet_store::key_type (vban::wallet_value const & value_a)
  {
  	auto number (value_a.key.number ());
  	vban::key_type result;
  	auto text (number.convert_to<std::string> ());
  	if (number > std::numeric_limits<uint64_t>::max ())
  	{
  		result = vban::key_type::adhoc;
  	}
  	else
  	{
  		if ((number >> 32).convert_to<uint32_t> () == 1)
  		{
  			result = vban::key_type::deterministic;
  		}
  		else
  		{
  			result = vban::key_type::unknown;
  		}
  	}
  	return result;
  }
  
  bool vban::wallet_store::fetch (vban::transaction const & transaction_a, vban::account const & pub, vban::raw_key & prv)
  {
  	auto result (false);
  	if (valid_password (transaction_a))
  	{
  		vban::wallet_value value (entry_get_raw (transaction_a, pub));
  		if (!value.key.is_zero ())
  		{
  			switch (key_type (value))
  			{
  				case vban::key_type::deterministic:
  				{
  					vban::raw_key seed_l;
  					seed (seed_l, transaction_a);
  					uint32_t index (static_cast<uint32_t> (value.key.number () & static_cast<uint32_t> (-1)));
  					prv = deterministic_key (transaction_a, index);
  					break;
  				}
  				case vban::key_type::adhoc:
  				{
  					// Ad-hoc keys
  					vban::raw_key password_l;
  					wallet_key (password_l, transaction_a);
  					prv.decrypt (value.key, password_l, pub.owords[0].number ());
  					break;
  				}
  				default:
  				{
  					result = true;
  					break;
  				}
  			}
  		}
  		else
  		{
  			result = true;
  		}
  	}
  	else
  	{
  		result = true;
  	}
  	if (!result)
  	{
  		vban::public_key compare (vban::pub_key (prv));
  		if (!(pub == compare))
  		{
  			result = true;
  		}
  	}
  	return result;
  }
  
  bool vban::wallet_store::valid_public_key (vban::public_key const & pub)
  {
  	return pub.number () >= special_count;
  }
  
  bool vban::wallet_store::exists (vban::transaction const & transaction_a, vban::public_key const & pub)
  {
  	return valid_public_key (pub) && find (transaction_a, pub) != end ();
  }
  
  void vban::wallet_store::serialize_json (vban::transaction const & transaction_a, std::string & string_a)
  {
  	boost::property_tree::ptree tree;
  	for (vban::store_iterator<vban::uint256_union, vban::wallet_value> i (std::make_unique<vban::mdb_iterator<vban::uint256_union, vban::wallet_value>> (transaction_a, handle)), n (nullptr); i != n; ++i)
  	{
  		tree.put (i->first.to_string (), i->second.key.to_string ());
  	}
  	std::stringstream ostream;
  	boost::property_tree::write_json (ostream, tree);
  	string_a = ostream.str ();
  }
  
  void vban::wallet_store::write_backup (vban::transaction const & transaction_a, boost::filesystem::path const & path_a)
  {
  	std::ofstream backup_file;
  	backup_file.open (path_a.string ());
  	if (!backup_file.fail ())
  	{
  		// Set permissions to 600
  		boost::system::error_code ec;
  		vban::set_secure_perm_file (path_a, ec);
  
  		std::string json;
  		serialize_json (transaction_a, json);
  		backup_file << json;
  	}
  }
  
  bool vban::wallet_store::move (vban::transaction const & transaction_a, vban::wallet_store & other_a, std::vector<vban::public_key> const & keys)
  {
  	debug_assert (valid_password (transaction_a));
  	debug_assert (other_a.valid_password (transaction_a));
  	auto result (false);
  	for (auto i (keys.begin ()), n (keys.end ()); i != n; ++i)
  	{
  		vban::raw_key prv;
  		auto error (other_a.fetch (transaction_a, *i, prv));
  		result = result | error;
  		if (!result)
  		{
  			insert_adhoc (transaction_a, prv);
  			other_a.erase (transaction_a, *i);
  		}
  	}
  	return result;
  }
  
  bool vban::wallet_store::import (vban::transaction const & transaction_a, vban::wallet_store & other_a)
  {
  	debug_assert (valid_password (transaction_a));
  	debug_assert (other_a.valid_password (transaction_a));
  	auto result (false);
  	for (auto i (other_a.begin (transaction_a)), n (end ()); i != n; ++i)
  	{
  		vban::raw_key prv;
  		auto error (other_a.fetch (transaction_a, i->first, prv));
  		result = result | error;
  		if (!result)
  		{
  			if (!prv.is_zero ())
  			{
  				insert_adhoc (transaction_a, prv);
  			}
  			else
  			{
  				insert_watch (transaction_a, i->first);
  			}
  			other_a.erase (transaction_a, i->first);
  		}
  	}
  	return result;
  }
  
  bool vban::wallet_store::work_get (vban::transaction const & transaction_a, vban::public_key const & pub_a, uint64_t & work_a)
  {
  	auto result (false);
  	auto entry (entry_get_raw (transaction_a, pub_a));
  	if (!entry.key.is_zero ())
  	{
  		work_a = entry.work;
  	}
  	else
  	{
  		result = true;
  	}
  	return result;
  }
  
  void vban::wallet_store::work_put (vban::transaction const & transaction_a, vban::public_key const & pub_a, uint64_t work_a)
  {
  	auto entry (entry_get_raw (transaction_a, pub_a));
  	debug_assert (!entry.key.is_zero ());
  	entry.work = work_a;
  	entry_put_raw (transaction_a, pub_a, entry);
  }
  
  unsigned vban::wallet_store::version (vban::transaction const & transaction_a)
  {
  	vban::wallet_value value (entry_get_raw (transaction_a, vban::wallet_store::version_special));
  	auto entry (value.key);
  	auto result (static_cast<unsigned> (entry.bytes[31]));
  	return result;
  }
  
  void vban::wallet_store::version_put (vban::transaction const & transaction_a, unsigned version_a)
  {
  	vban::raw_key entry (version_a);
  	entry_put_raw (transaction_a, vban::wallet_store::version_special, vban::wallet_value (entry, 0));
  }
  
  void vban::kdf::phs (vban::raw_key & result_a, std::string const & password_a, vban::uint256_union const & salt_a)
  {
  	static vban::network_params network_params;
  	vban::lock_guard<vban::mutex> lock (mutex);
  	auto success (argon2_hash (1, network_params.kdf_work, 1, password_a.data (), password_a.size (), salt_a.bytes.data (), salt_a.bytes.size (), result_a.bytes.data (), result_a.bytes.size (), NULL, 0, Argon2_d, 0x10));
  	debug_assert (success == 0);
  	(void)success;
  }
  
  vban::wallet::wallet (bool & init_a, vban::transaction & transaction_a, vban::wallets & wallets_a, std::string const & wallet_a) :
  	lock_observer ([] (bool, bool) {}),
  	store (init_a, wallets_a.kdf, transaction_a, wallets_a.node.config.random_representative (), wallets_a.node.config.password_fanout, wallet_a),
  	wallets (wallets_a)
  {
  }
  
  vban::wallet::wallet (bool & init_a, vban::transaction & transaction_a, vban::wallets & wallets_a, std::string const & wallet_a, std::string const & json) :
  	lock_observer ([] (bool, bool) {}),
  	store (init_a, wallets_a.kdf, transaction_a, wallets_a.node.config.random_representative (), wallets_a.node.config.password_fanout, wallet_a, json),
  	wallets (wallets_a)
  {
  }
  
  void vban::wallet::enter_initial_password ()
  {
  	vban::raw_key password_l;
  	{
  		vban::lock_guard<std::recursive_mutex> lock (store.mutex);
  		store.password.value (password_l);
  	}
  	if (password_l.is_zero ())
  	{
  		auto transaction (wallets.tx_begin_write ());
  		if (store.valid_password (transaction))
  		{
  			// Newly created wallets have a zero key
  			store.rekey (transaction, "");
  		}
  		else
  		{
  			enter_password (transaction, "");
  		}
  	}
  }
  
  bool vban::wallet::enter_password (vban::transaction const & transaction_a, std::string const & password_a)
  {
  	auto result (store.attempt_password (transaction_a, password_a));
  	if (!result)
  	{
  		auto this_l (shared_from_this ());
  		wallets.node.background ([this_l] () {
  			this_l->search_pending (this_l->wallets.tx_begin_read ());
  		});
  		wallets.node.logger.try_log ("Wallet unlocked");
  	}
  	else
  	{
  		wallets.node.logger.try_log ("Invalid password, wallet locked");
  	}
  	lock_observer (result, password_a.empty ());
  	return result;
  }
  
  vban::public_key vban::wallet::deterministic_insert (vban::transaction const & transaction_a, bool generate_work_a)
  {
  	vban::public_key key (0);
  	if (store.valid_password (transaction_a))
  	{
  		key = store.deterministic_insert (transaction_a);
  		if (generate_work_a)
  		{
  			work_ensure (key, key);
  		}
  		auto half_principal_weight (wallets.node.minimum_principal_weight () / 2);
  		if (wallets.check_rep (key, half_principal_weight))
  		{
  			vban::lock_guard<vban::mutex> lock (representatives_mutex);
  			representatives.insert (key);
  		}
  	}
  	return key;
  }
  
  vban::public_key vban::wallet::deterministic_insert (uint32_t const index, bool generate_work_a)
  {
  	auto transaction (wallets.tx_begin_write ());
  	vban::public_key key (0);
  	if (store.valid_password (transaction))
  	{
  		key = store.deterministic_insert (transaction, index);
  		if (generate_work_a)
  		{
  			work_ensure (key, key);
  		}
  	}
  	return key;
  }
  
  vban::public_key vban::wallet::deterministic_insert (bool generate_work_a)
  {
  	auto transaction (wallets.tx_begin_write ());
  	auto result (deterministic_insert (transaction, generate_work_a));
  	return result;
  }
  
  vban::public_key vban::wallet::insert_adhoc (vban::raw_key const & key_a, bool generate_work_a)
  {
  	vban::public_key key (0);
  	auto transaction (wallets.tx_begin_write ());
  	if (store.valid_password (transaction))
  	{
  		key = store.insert_adhoc (transaction, key_a);
  		auto block_transaction (wallets.node.store.tx_begin_read ());
  		if (generate_work_a)
  		{
  			work_ensure (key, wallets.node.ledger.latest_root (block_transaction, key));
  		}
  		auto half_principal_weight (wallets.node.minimum_principal_weight () / 2);
  		// Makes sure that the representatives container will
  		// be in sync with any added keys.
  		transaction.commit ();
  		if (wallets.check_rep (key, half_principal_weight))
  		{
  			vban::lock_guard<vban::mutex> lock (representatives_mutex);
  			representatives.insert (key);
  		}
  	}
  	return key;
  }
  
  bool vban::wallet::insert_watch (vban::transaction const & transaction_a, vban::public_key const & pub_a)
  {
  	return store.insert_watch (transaction_a, pub_a);
  }
  
  bool vban::wallet::exists (vban::public_key const & account_a)
  {
  	auto transaction (wallets.tx_begin_read ());
  	return store.exists (transaction, account_a);
  }
  
  bool vban::wallet::import (std::string const & json_a, std::string const & password_a)
  {
  	auto error (false);
  	std::unique_ptr<vban::wallet_store> temp;
  	{
  		auto transaction (wallets.tx_begin_write ());
  		vban::uint256_union id;
  		random_pool::generate_block (id.bytes.data (), id.bytes.size ());
  		temp = std::make_unique<vban::wallet_store> (error, wallets.node.wallets.kdf, transaction, 0, 1, id.to_string (), json_a);
  	}
  	if (!error)
  	{
  		auto transaction (wallets.tx_begin_write ());
  		error = temp->attempt_password (transaction, password_a);
  	}
  	auto transaction (wallets.tx_begin_write ());
  	if (!error)
  	{
  		error = store.import (transaction, *temp);
  	}
  	temp->destroy (transaction);
  	return error;
  }
  
  void vban::wallet::serialize (std::string & json_a)
  {
  	auto transaction (wallets.tx_begin_read ());
  	store.serialize_json (transaction, json_a);
  }
  
  void vban::wallet_store::destroy (vban::transaction const & transaction_a)
  {
  	auto status (mdb_drop (tx (transaction_a), handle, 1));
  	(void)status;
  	debug_assert (status == 0);
  	handle = 0;
  }
  
  std::shared_ptr<vban::block> vban::wallet::receive_action (vban::block_hash const & send_hash_a, vban::account const & representative_a, vban::uint128_union const & amount_a, vban::account const & account_a, uint64_t work_a, bool generate_work_a)
  {
  	std::shared_ptr<vban::block> block;
  	vban::block_details details;
  	details.is_receive = true;
  	if (wallets.node.config.receive_minimum.number () <= amount_a.number ())
  	{
  		auto block_transaction (wallets.node.ledger.store.tx_begin_read ());
  		auto transaction (wallets.tx_begin_read ());
  		vban::pending_info pending_info;
  		if (wallets.node.ledger.block_or_pruned_exists (block_transaction, send_hash_a))
  		{
  			if (!wallets.node.ledger.store.pending_get (block_transaction, vban::pending_key (account_a, send_hash_a), pending_info))
  			{
  				vban::raw_key prv;
  				if (!store.fetch (transaction, account_a, prv))
  				{
  					if (work_a == 0)
  					{
  						store.work_get (transaction, account_a, work_a);
  					}
  					vban::account_info info;
  					auto new_account (wallets.node.ledger.store.account_get (block_transaction, account_a, info));
  					if (!new_account)
  					{
  						block = std::make_shared<vban::state_block> (account_a, info.head, info.representative, info.balance.number () + pending_info.amount.number (), send_hash_a, prv, account_a, work_a);
  						details.epoch = std::max (info.epoch (), pending_info.epoch);
  					}
  					else
  					{
  						block = std::make_shared<vban::state_block> (account_a, 0, representative_a, pending_info.amount, reinterpret_cast<vban::link const &> (send_hash_a), prv, account_a, work_a);
  						details.epoch = pending_info.epoch;
  					}
  				}
  				else
  				{
  					wallets.node.logger.try_log ("Unable to receive, wallet locked");
  				}
  			}
  			else
  			{
  				// Ledger doesn't have this marked as available to receive anymore
  			}
  		}
  		else
  		{
  			// Ledger doesn't have this block anymore.
  		}
  	}
  	else
  	{
  		wallets.node.logger.try_log (boost::str (boost::format ("Not receiving block %1% due to minimum receive threshold") % send_hash_a.to_string ()));
  		// Someone sent us something below the threshold of receiving
  	}
  	if (block != nullptr)
  	{
  		if (action_complete (block, account_a, generate_work_a, details))
  		{
  			// Return null block after work generation or ledger process error
  			block = nullptr;
  		}
  	}
  	return block;
  }
  
  std::shared_ptr<vban::block> vban::wallet::change_action (vban::account const & source_a, vban::account const & representative_a, uint64_t work_a, bool generate_work_a)
  {
  	std::shared_ptr<vban::block> block;
  	vban::block_details details;
  	{
  		auto transaction (wallets.tx_begin_read ());
  		auto block_transaction (wallets.node.store.tx_begin_read ());
  		if (store.valid_password (transaction))
  		{
  			auto existing (store.find (transaction, source_a));
  			if (existing != store.end () && !wallets.node.ledger.latest (block_transaction, source_a).is_zero ())
  			{
  				vban::account_info info;
  				auto error1 (wallets.node.ledger.store.account_get (block_transaction, source_a, info));
  				(void)error1;
  				debug_assert (!error1);
  				vban::raw_key prv;
  				auto error2 (store.fetch (transaction, source_a, prv));
  				(void)error2;
  				debug_assert (!error2);
  				if (work_a == 0)
  				{
  					store.work_get (transaction, source_a, work_a);
  				}
  				block = std::make_shared<vban::state_block> (source_a, info.head, representative_a, info.balance, 0, prv, source_a, work_a);
  				details.epoch = info.epoch ();
  			}
  		}
  	}
  	if (block != nullptr)
  	{
  		if (action_complete (block, source_a, generate_work_a, details))
  		{
  			// Return null block after work generation or ledger process error
  			block = nullptr;
  		}
  	}
  	return block;
  }
  
  std::shared_ptr<vban::block> vban::wallet::send_action (vban::account const & source_a, vban::account const & account_a, vban::uint256_t const & amount_a, uint64_t work_a, bool generate_work_a, boost::optional<std::string> id_a)
  {
  	boost::optional<vban::mdb_val> id_mdb_val;
  	if (id_a)
  	{
  		id_mdb_val = vban::mdb_val (id_a->size (), const_cast<char *> (id_a->data ()));
  	}
  
  	auto prepare_send = [&id_mdb_val, &wallets = this->wallets, &store = this->store, &source_a, &amount_a, &work_a, &account_a] (const auto & transaction) {
  		auto block_transaction (wallets.node.store.tx_begin_read ());
  		auto error (false);
  		auto cached_block (false);
  		std::shared_ptr<vban::block> block;
  		vban::block_details details;
  		details.is_send = true;
  		if (id_mdb_val)
  		{
  			vban::mdb_val result;
  			auto status (mdb_get (wallets.env.tx (transaction), wallets.node.wallets.send_action_ids, *id_mdb_val, result));
  			if (status == 0)
  			{
  				vban::block_hash hash (result);
  				block = wallets.node.store.block_get (block_transaction, hash);
  				if (block != nullptr)
  				{
  					cached_block = true;
  					wallets.node.network.flood_block (block, vban::buffer_drop_policy::no_limiter_drop);
  				}
  			}
  			else if (status != MDB_NOTFOUND)
  			{
  				error = true;
  			}
  		}
  		if (!error && block == nullptr)
  		{
  			if (store.valid_password (transaction))
  			{
  				auto existing (store.find (transaction, source_a));
  				if (existing != store.end ())
  				{
  					auto balance (wallets.node.ledger.account_balance (block_transaction, source_a));
  					if (!balance.is_zero () && balance >= amount_a)
  					{
  						vban::account_info info;
  						auto error1 (wallets.node.ledger.store.account_get (block_transaction, source_a, info));
  						(void)error1;
  						debug_assert (!error1);
  						vban::raw_key prv;
  						auto error2 (store.fetch (transaction, source_a, prv));
  						(void)error2;
  						debug_assert (!error2);
  						if (work_a == 0)
  						{
  							store.work_get (transaction, source_a, work_a);
  						}
  						block = std::make_shared<vban::state_block> (source_a, info.head, info.representative, balance - amount_a, account_a, prv, source_a, work_a);
  						details.epoch = info.epoch ();
  						if (id_mdb_val && block != nullptr)
  						{
  							auto status (mdb_put (wallets.env.tx (transaction), wallets.node.wallets.send_action_ids, *id_mdb_val, vban::mdb_val (block->hash ()), 0));
  							if (status != 0)
  							{
  								block = nullptr;
  								error = true;
  							}
  						}
  					}
  				}
  			}
  		}
  		return std::make_tuple (block, error, cached_block, details);
  	};
  
  	std::tuple<std::shared_ptr<vban::block>, bool, bool, vban::block_details> result;
  	{
  		if (id_mdb_val)
  		{
  			result = prepare_send (wallets.tx_begin_write ());
  		}
  		else
  		{
  			result = prepare_send (wallets.tx_begin_read ());
  		}
  	}
  
  	std::shared_ptr<vban::block> block;
  	bool error;
  	bool cached_block;
  	vban::block_details details;
  	std::tie (block, error, cached_block, details) = result;
  
  	if (!error && block != nullptr && !cached_block)
  	{
  		if (action_complete (block, source_a, generate_work_a, details))
  		{
  			// Return null block after work generation or ledger process error
  			block = nullptr;
  		}
  	}
  	return block;
  }
  
  bool vban::wallet::action_complete (std::shared_ptr<vban::block> const & block_a, vban::account const & account_a, bool const generate_work_a, vban::block_details const & details_a)
  {
  	bool error{ false };
  	// Unschedule any work caching for this account
  	wallets.delayed_work->erase (account_a);
  	if (block_a != nullptr)
  	{
  		auto required_difficulty{ vban::work_threshold (block_a->work_version (), details_a) };
  		if (block_a->difficulty () < required_difficulty)
  		{
  			wallets.node.logger.try_log (boost::str (boost::format ("Cached or provided work for block %1% account %2% is invalid, regenerating") % block_a->hash ().to_string () % account_a.to_account ()));
  			debug_assert (required_difficulty <= wallets.node.max_work_generate_difficulty (block_a->work_version ()));
  			error = !wallets.node.work_generate_blocking (*block_a, required_difficulty).is_initialized ();
  		}
  		if (!error)
  		{
  			error = wallets.node.process_local (block_a).code != vban::process_result::progress;
  			debug_assert (error || block_a->sideband ().details == details_a);
  		}
  		if (!error && generate_work_a)
  		{
  			work_ensure (account_a, block_a->hash ());
  		}
  	}
  	return error;
  }
  
  bool vban::wallet::change_sync (vban::account const & source_a, vban::account const & representative_a)
  {
  	std::promise<bool> result;
  	std::future<bool> future = result.get_future ();
  	change_async (
  	source_a, representative_a, [&result] (std::shared_ptr<vban::block> const & block_a) {
  		result.set_value (block_a == nullptr);
  	},
  	true);
  	return future.get ();
  }
  
  void vban::wallet::change_async (vban::account const & source_a, vban::account const & representative_a, std::function<void (std::shared_ptr<vban::block> const &)> const & action_a, uint64_t work_a, bool generate_work_a)
  {
  	auto this_l (shared_from_this ());
  	wallets.node.wallets.queue_wallet_action (vban::wallets::high_priority, this_l, [this_l, source_a, representative_a, action_a, work_a, generate_work_a] (vban::wallet & wallet_a) {
  		auto block (wallet_a.change_action (source_a, representative_a, work_a, generate_work_a));
  		action_a (block);
  	});
  }
  
  bool vban::wallet::receive_sync (std::shared_ptr<vban::block> const & block_a, vban::account const & representative_a, vban::uint256_t const & amount_a)
  {
  	std::promise<bool> result;
  	std::future<bool> future = result.get_future ();
  	auto destination (block_a->link ().is_zero () ? block_a->destination () : block_a->link ().as_account ());
  	receive_async (
  	block_a->hash (), representative_a, amount_a, destination, [&result] (std::shared_ptr<vban::block> const & block_a) {
  		result.set_value (block_a == nullptr);
  	},
  	true);
  	return future.get ();
  }
  
  void vban::wallet::receive_async (vban::block_hash const & hash_a, vban::account const & representative_a, vban::uint256_t const & amount_a, vban::account const & account_a, std::function<void (std::shared_ptr<vban::block> const &)> const & action_a, uint64_t work_a, bool generate_work_a)
  {
  	auto this_l (shared_from_this ());
  	wallets.node.wallets.queue_wallet_action (amount_a, this_l, [this_l, hash_a, representative_a, amount_a, account_a, action_a, work_a, generate_work_a] (vban::wallet & wallet_a) {
  		auto block (wallet_a.receive_action (hash_a, representative_a, amount_a, account_a, work_a, generate_work_a));
  		action_a (block);
  	});
  }
  
  vban::block_hash vban::wallet::send_sync (vban::account const & source_a, vban::account const & account_a, vban::uint256_t const & amount_a)
  {
  	std::promise<vban::block_hash> result;
  	std::future<vban::block_hash> future = result.get_future ();
  	send_async (
  	source_a, account_a, amount_a, [&result] (std::shared_ptr<vban::block> const & block_a) {
  		result.set_value (block_a->hash ());
  	},
  	true);
  	return future.get ();
  }
  
  void vban::wallet::send_async (vban::account const & source_a, vban::account const & account_a, vban::uint256_t const & amount_a, std::function<void (std::shared_ptr<vban::block> const &)> const & action_a, uint64_t work_a, bool generate_work_a, boost::optional<std::string> id_a)
  {
  	auto this_l (shared_from_this ());
  	wallets.node.wallets.queue_wallet_action (vban::wallets::high_priority, this_l, [this_l, source_a, account_a, amount_a, action_a, work_a, generate_work_a, id_a] (vban::wallet & wallet_a) {
  		auto block (wallet_a.send_action (source_a, account_a, amount_a, work_a, generate_work_a, id_a));
  		action_a (block);
  	});
  }
  
  // Update work for account if latest root is root_a
  void vban::wallet::work_update (vban::transaction const & transaction_a, vban::account const & account_a, vban::root const & root_a, uint64_t work_a)
  {
  	debug_assert (!vban::work_validate_entry (vban::work_version::work_1, root_a, work_a));
  	debug_assert (store.exists (transaction_a, account_a));
  	auto block_transaction (wallets.node.store.tx_begin_read ());
  	auto latest (wallets.node.ledger.latest_root (block_transaction, account_a));
  	if (latest == root_a)
  	{
  		store.work_put (transaction_a, account_a, work_a);
  	}
  	else
  	{
  		wallets.node.logger.try_log ("Cached work no longer valid, discarding");
  	}
  }
  
  void vban::wallet::work_ensure (vban::account const & account_a, vban::root const & root_a)
  {
  	using namespace std::chrono_literals;
  	std::chrono::seconds const precache_delay = wallets.node.network_params.network.is_dev_network () ? 1s : 10s;
  
  	wallets.delayed_work->operator[] (account_a) = root_a;
  
  	wallets.node.workers.add_timed_task (std::chrono::steady_clock::now () + precache_delay, [this_l = shared_from_this (), account_a, root_a] {
  		auto delayed_work = this_l->wallets.delayed_work.lock ();
  		auto existing (delayed_work->find (account_a));
  		if (existing != delayed_work->end () && existing->second == root_a)
  		{
  			delayed_work->erase (existing);
  			this_l->wallets.queue_wallet_action (vban::wallets::generate_priority, this_l, [account_a, root_a] (vban::wallet & wallet_a) {
  				wallet_a.work_cache_blocking (account_a, root_a);
  			});
  		}
  	});
  }
  
  bool vban::wallet::search_pending (vban::transaction const & wallet_transaction_a)
  {
  	auto result (!store.valid_password (wallet_transaction_a));
  	if (!result)
  	{
  		wallets.node.logger.try_log ("Beginning pending block search");
  		for (auto i (store.begin (wallet_transaction_a)), n (store.end ()); i != n; ++i)
  		{
  			auto block_transaction (wallets.node.store.tx_begin_read ());
  			vban::account const & account (i->first);
  			// Don't search pending for watch-only accounts
  			if (!vban::wallet_value (i->second).key.is_zero ())
  			{
  				for (auto j (wallets.node.store.pending_begin (block_transaction, vban::pending_key (account, 0))), k (wallets.node.store.pending_end ()); j != k && vban::pending_key (j->first).account == account; ++j)
  				{
  					vban::pending_key key (j->first);
  					auto hash (key.hash);
  					vban::pending_info pending (j->second);
  					auto amount (pending.amount.number ());
  					if (wallets.node.config.receive_minimum.number () <= amount)
  					{
  						wallets.node.logger.try_log (boost::str (boost::format ("Found a pending block %1% for account %2%") % hash.to_string () % pending.source.to_account ()));
  						if (wallets.node.ledger.block_confirmed (block_transaction, hash))
  						{
  							auto representative = store.representative (wallet_transaction_a);
  							// Receive confirmed block
  							receive_async (hash, representative, amount, account, [] (std::shared_ptr<vban::block> const &) {});
  						}
  						else if (!wallets.node.confirmation_height_processor.is_processing_block (hash))
  						{
  							auto block (wallets.node.store.block_get (block_transaction, hash));
  							if (block)
  							{
  								// Request confirmation for block which is not being processed yet
  								wallets.node.block_confirm (block);
  							}
  						}
  					}
  				}
  			}
  		}
  		wallets.node.logger.try_log ("Pending block search phase complete");
  	}
  	else
  	{
  		wallets.node.logger.try_log ("Stopping search, wallet is locked");
  	}
  	return result;
  }
  
  void vban::wallet::init_free_accounts (vban::transaction const & transaction_a)
  {
  	free_accounts.clear ();
  	for (auto i (store.begin (transaction_a)), n (store.end ()); i != n; ++i)
  	{
  		free_accounts.insert (i->first);
  	}
  }
  
  uint32_t vban::wallet::deterministic_check (vban::transaction const & transaction_a, uint32_t index)
  {
  	auto block_transaction (wallets.node.store.tx_begin_read ());
  	for (uint32_t i (index + 1), n (index + 64); i < n; ++i)
  	{
  		auto prv = store.deterministic_key (transaction_a, i);
  		vban::keypair pair (prv.to_string ());
  		// Check if account received at least 1 block
  		auto latest (wallets.node.ledger.latest (block_transaction, pair.pub));
  		if (!latest.is_zero ())
  		{
  			index = i;
  			// i + 64 - Check additional 64 accounts
  			// i/64 - Check additional accounts for large wallets. I.e. 64000/64 = 1000 accounts to check
  			n = i + 64 + (i / 64);
  		}
  		else
  		{
  			// Check if there are pending blocks for account
  			for (auto ii (wallets.node.store.pending_begin (block_transaction, vban::pending_key (pair.pub, 0))), nn (wallets.node.store.pending_end ()); ii != nn && vban::pending_key (ii->first).account == pair.pub; ++ii)
  			{
  				index = i;
  				n = i + 64 + (i / 64);
  				break;
  			}
  		}
  	}
  	return index;
  }
  
  vban::public_key vban::wallet::change_seed (vban::transaction const & transaction_a, vban::raw_key const & prv_a, uint32_t count)
  {
  	store.seed_set (transaction_a, prv_a);
  	auto account = deterministic_insert (transaction_a);
  	if (count == 0)
  	{
  		count = deterministic_check (transaction_a, 0);
  	}
  	for (uint32_t i (0); i < count; ++i)
  	{
  		// Disable work generation to prevent weak CPU nodes stuck
  		account = deterministic_insert (transaction_a, false);
  	}
  	return account;
  }
  
  void vban::wallet::deterministic_restore (vban::transaction const & transaction_a)
  {
  	auto index (store.deterministic_index_get (transaction_a));
  	auto new_index (deterministic_check (transaction_a, index));
  	for (uint32_t i (index); i <= new_index && index != new_index; ++i)
  	{
  		// Disable work generation to prevent weak CPU nodes stuck
  		deterministic_insert (transaction_a, false);
  	}
  }
  
  bool vban::wallet::live ()
  {
  	return store.handle != 0;
  }
  
  void vban::wallet::work_cache_blocking (vban::account const & account_a, vban::root const & root_a)
  {
  	if (wallets.node.work_generation_enabled ())
  	{
  		auto difficulty (wallets.node.default_difficulty (vban::work_version::work_1));
  		auto opt_work_l (wallets.node.work_generate_blocking (vban::work_version::work_1, root_a, difficulty, account_a));
  		if (opt_work_l.is_initialized ())
  		{
  			auto transaction_l (wallets.tx_begin_write ());
  			if (live () && store.exists (transaction_l, account_a))
  			{
  				work_update (transaction_l, account_a, root_a, *opt_work_l);
  			}
  		}
  		else if (!wallets.node.stopped)
  		{
  			wallets.node.logger.try_log (boost::str (boost::format ("Could not precache work for root %1% due to work generation failure") % root_a.to_string ()));
  		}
  	}
  }
  
  void vban::wallets::do_wallet_actions ()
  {
  	vban::unique_lock<vban::mutex> action_lock (action_mutex);
  	while (!stopped)
  	{
  		if (!actions.empty ())
  		{
  			auto first (actions.begin ());
  			auto wallet (first->second.first);
  			auto current (std::move (first->second.second));
  			actions.erase (first);
  			if (wallet->live ())
  			{
  				action_lock.unlock ();
  				observer (true);
  				current (*wallet);
  				observer (false);
  				action_lock.lock ();
  			}
  		}
  		else
  		{
  			condition.wait (action_lock);
  		}
  	}
  }
  
  vban::wallets::wallets (bool error_a, vban::node & node_a) :
  	observer ([] (bool) {}),
  	node (node_a),
  	env (boost::polymorphic_downcast<vban::mdb_wallets_store *> (node_a.wallets_store_impl.get ())->environment),
  	stopped (false),
  	thread ([this] () {
  		vban::thread_role::set (vban::thread_role::name::wallet_actions);
  		do_wallet_actions ();
  	})
  {
  	vban::unique_lock<vban::mutex> lock (mutex);
  	if (!error_a)
  	{
  		auto transaction (tx_begin_write ());
  		auto status (mdb_dbi_open (env.tx (transaction), nullptr, MDB_CREATE, &handle));
  		split_if_needed (transaction, node.store);
  		status |= mdb_dbi_open (env.tx (transaction), "send_action_ids", MDB_CREATE, &send_action_ids);
  		debug_assert (status == 0);
  		std::string beginning (vban::uint256_union (0).to_string ());
  		std::string end ((vban::uint256_union (vban::uint256_t (0) - vban::uint256_t (1))).to_string ());
  		vban::store_iterator<std::array<char, 64>, vban::no_value> i (std::make_unique<vban::mdb_iterator<std::array<char, 64>, vban::no_value>> (transaction, handle, vban::mdb_val (beginning.size (), const_cast<char *> (beginning.c_str ()))));
  		vban::store_iterator<std::array<char, 64>, vban::no_value> n (std::make_unique<vban::mdb_iterator<std::array<char, 64>, vban::no_value>> (transaction, handle, vban::mdb_val (end.size (), const_cast<char *> (end.c_str ()))));
  		for (; i != n; ++i)
  		{
  			vban::wallet_id id;
  			std::string text (i->first.data (), i->first.size ());
  			auto error (id.decode_hex (text));
  			debug_assert (!error);
  			debug_assert (items.find (id) == items.end ());
  			auto wallet (std::make_shared<vban::wallet> (error, transaction, *this, text));
  			if (!error)
  			{
  				items[id] = wallet;
  			}
  			else
  			{
  				// Couldn't open wallet
  			}
  		}
  	}
  	// Backup before upgrade wallets
  	bool backup_required (false);
  	if (node.config.backup_before_upgrade)
  	{
  		auto transaction (tx_begin_read ());
  		for (auto & item : items)
  		{
  			if (item.second->store.version (transaction) != vban::wallet_store::version_current)
  			{
  				backup_required = true;
  				break;
  			}
  		}
  	}
  	if (backup_required)
  	{
  		const char * store_path;
  		mdb_env_get_path (env, &store_path);
  		const boost::filesystem::path path (store_path);
  		vban::mdb_store::create_backup_file (env, path, node_a.logger);
  	}
  	for (auto & item : items)
  	{
  		item.second->enter_initial_password ();
  	}
  	if (node_a.config.enable_voting)
  	{
  		lock.unlock ();
  		ongoing_compute_reps ();
  	}
  }
  
  vban::wallets::~wallets ()
  {
  	stop ();
  }
  
  std::shared_ptr<vban::wallet> vban::wallets::open (vban::wallet_id const & id_a)
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	std::shared_ptr<vban::wallet> result;
  	auto existing (items.find (id_a));
  	if (existing != items.end ())
  	{
  		result = existing->second;
  	}
  	return result;
  }
  
  std::shared_ptr<vban::wallet> vban::wallets::create (vban::wallet_id const & id_a)
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	debug_assert (items.find (id_a) == items.end ());
  	std::shared_ptr<vban::wallet> result;
  	bool error;
  	{
  		auto transaction (tx_begin_write ());
  		result = std::make_shared<vban::wallet> (error, transaction, *this, id_a.to_string ());
  	}
  	if (!error)
  	{
  		items[id_a] = result;
  		result->enter_initial_password ();
  	}
  	return result;
  }
  
  bool vban::wallets::search_pending (vban::wallet_id const & wallet_a)
  {
  	auto result (false);
  	if (auto wallet = open (wallet_a); wallet != nullptr)
  	{
  		result = wallet->search_pending (tx_begin_read ());
  	}
  	return result;
  }
  
  void vban::wallets::search_pending_all ()
  {
  	vban::unique_lock<vban::mutex> lk (mutex);
  	auto wallets_l = get_wallets ();
  	auto wallet_transaction (tx_begin_read ());
  	lk.unlock ();
  	for (auto const & [id, wallet] : wallets_l)
  	{
  		wallet->search_pending (wallet_transaction);
  	}
  }
  
  void vban::wallets::destroy (vban::wallet_id const & id_a)
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	auto transaction (tx_begin_write ());
  	// action_mutex should be after transactions to prevent deadlocks in deterministic_insert () & insert_adhoc ()
  	vban::lock_guard<vban::mutex> action_lock (action_mutex);
  	auto existing (items.find (id_a));
  	debug_assert (existing != items.end ());
  	auto wallet (existing->second);
  	items.erase (existing);
  	wallet->store.destroy (transaction);
  }
  
  void vban::wallets::reload ()
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	auto transaction (tx_begin_write ());
  	std::unordered_set<vban::uint256_union> stored_items;
  	std::string beginning (vban::uint256_union (0).to_string ());
  	std::string end ((vban::uint256_union (vban::uint256_t (0) - vban::uint256_t (1))).to_string ());
  	vban::store_iterator<std::array<char, 64>, vban::no_value> i (std::make_unique<vban::mdb_iterator<std::array<char, 64>, vban::no_value>> (transaction, handle, vban::mdb_val (beginning.size (), const_cast<char *> (beginning.c_str ()))));
  	vban::store_iterator<std::array<char, 64>, vban::no_value> n (std::make_unique<vban::mdb_iterator<std::array<char, 64>, vban::no_value>> (transaction, handle, vban::mdb_val (end.size (), const_cast<char *> (end.c_str ()))));
  	for (; i != n; ++i)
  	{
  		vban::wallet_id id;
  		std::string text (i->first.data (), i->first.size ());
  		auto error (id.decode_hex (text));
  		debug_assert (!error);
  		// New wallet
  		if (items.find (id) == items.end ())
  		{
  			auto wallet (std::make_shared<vban::wallet> (error, transaction, *this, text));
  			if (!error)
  			{
  				items[id] = wallet;
  			}
  		}
  		// List of wallets on disk
  		stored_items.insert (id);
  	}
  	// Delete non existing wallets from memory
  	std::vector<vban::wallet_id> deleted_items;
  	for (auto i : items)
  	{
  		if (stored_items.find (i.first) == stored_items.end ())
  		{
  			deleted_items.push_back (i.first);
  		}
  	}
  	for (auto & i : deleted_items)
  	{
  		debug_assert (items.find (i) == items.end ());
  		items.erase (i);
  	}
  }
  
  void vban::wallets::queue_wallet_action (vban::uint256_t const & amount_a, std::shared_ptr<vban::wallet> const & wallet_a, std::function<void (vban::wallet &)> action_a)
  {
  	{
  		vban::lock_guard<vban::mutex> action_lock (action_mutex);
  		actions.emplace (amount_a, std::make_pair (wallet_a, action_a));
  	}
  	condition.notify_all ();
  }
  
  void vban::wallets::foreach_representative (std::function<void (vban::public_key const & pub_a, vban::raw_key const & prv_a)> const & action_a)
  {
  	if (node.config.enable_voting)
  	{
  		std::vector<std::pair<vban::public_key const, vban::raw_key const>> action_accounts_l;
  		{
  			auto transaction_l (tx_begin_read ());
  			vban::lock_guard<vban::mutex> lock (mutex);
  			for (auto i (items.begin ()), n (items.end ()); i != n; ++i)
  			{
  				auto & wallet (*i->second);
  				vban::lock_guard<std::recursive_mutex> store_lock (wallet.store.mutex);
  				decltype (wallet.representatives) representatives_l;
  				{
  					vban::lock_guard<vban::mutex> representatives_lock (wallet.representatives_mutex);
  					representatives_l = wallet.representatives;
  				}
  				for (auto const & account : representatives_l)
  				{
  					if (wallet.store.exists (transaction_l, account))
  					{
  						if (!node.ledger.weight (account).is_zero ())
  						{
  							if (wallet.store.valid_password (transaction_l))
  							{
  								vban::raw_key prv;
  								auto error (wallet.store.fetch (transaction_l, account, prv));
  								(void)error;
  								debug_assert (!error);
  								action_accounts_l.emplace_back (account, prv);
  							}
  							else
  							{
  								static auto last_log = std::chrono::steady_clock::time_point ();
  								if (last_log < std::chrono::steady_clock::now () - std::chrono::seconds (60))
  								{
  									last_log = std::chrono::steady_clock::now ();
  									node.logger.always_log (boost::str (boost::format ("Representative locked inside wallet %1%") % i->first.to_string ()));
  								}
  							}
  						}
  					}
  				}
  			}
  		}
  		for (auto const & representative : action_accounts_l)
  		{
  			action_a (representative.first, representative.second);
  		}
  	}
  }
  
  bool vban::wallets::exists (vban::transaction const & transaction_a, vban::account const & account_a)
  {
  	vban::lock_guard<vban::mutex> lock (mutex);
  	auto result (false);
  	for (auto i (items.begin ()), n (items.end ()); !result && i != n; ++i)
  	{
  		result = i->second->store.exists (transaction_a, account_a);
  	}
  	return result;
  }
  
  void vban::wallets::stop ()
  {
  	{
  		vban::lock_guard<vban::mutex> action_lock (action_mutex);
  		stopped = true;
  		actions.clear ();
  	}
  	condition.notify_all ();
  	if (thread.joinable ())
  	{
  		thread.join ();
  	}
  }
  
  vban::write_transaction vban::wallets::tx_begin_write ()
  {
  	return env.tx_begin_write ();
  }
  
  vban::read_transaction vban::wallets::tx_begin_read ()
  {
  	return env.tx_begin_read ();
  }
  
  void vban::wallets::clear_send_ids (vban::transaction const & transaction_a)
  {
  	auto status (mdb_drop (env.tx (transaction_a), send_action_ids, 0));
  	(void)status;
  	debug_assert (status == 0);
  }
  
  vban::wallet_representatives vban::wallets::reps () const
  {
  	vban::lock_guard<vban::mutex> counts_guard (reps_cache_mutex);
  	return representatives;
  }
  
  bool vban::wallets::check_rep (vban::account const & account_a, vban::uint256_t const & half_principal_weight_a, const bool acquire_lock_a)
  {
  	bool result (false);
  	auto weight (node.ledger.weight (account_a));
  	if (weight >= node.config.vote_minimum.number ())
  	{
  		vban::unique_lock<vban::mutex> lock;
  		if (acquire_lock_a)
  		{
  			lock = vban::unique_lock<vban::mutex> (reps_cache_mutex);
  		}
  		result = true;
  		representatives.accounts.insert (account_a);
  		++representatives.voting;
  		if (weight >= half_principal_weight_a)
  		{
  			++representatives.half_principal;
  		}
  	}
  	return result;
  }
  
  void vban::wallets::compute_reps ()
  {
  	vban::lock_guard<vban::mutex> guard (mutex);
  	vban::lock_guard<vban::mutex> counts_guard (reps_cache_mutex);
  	representatives.clear ();
  	auto half_principal_weight (node.minimum_principal_weight () / 2);
  	auto transaction (tx_begin_read ());
  	for (auto i (items.begin ()), n (items.end ()); i != n; ++i)
  	{
  		auto & wallet (*i->second);
  		decltype (wallet.representatives) representatives_l;
  		for (auto ii (wallet.store.begin (transaction)), nn (wallet.store.end ()); ii != nn; ++ii)
  		{
  			auto account (ii->first);
  			if (check_rep (account, half_principal_weight, false))
  			{
  				representatives_l.insert (account);
  			}
  		}
  		vban::lock_guard<vban::mutex> representatives_guard (wallet.representatives_mutex);
  		wallet.representatives.swap (representatives_l);
  	}
  }
  
  void vban::wallets::ongoing_compute_reps ()
  {
  	compute_reps ();
  	auto & node_l (node);
  	auto compute_delay (network_params.network.is_dev_network () ? std::chrono::milliseconds (10) : std::chrono::milliseconds (15 * 60 * 1000)); // Representation drifts quickly on the test network but very slowly on the live network
  	node.workers.add_timed_task (std::chrono::steady_clock::now () + compute_delay, [&node_l] () {
  		node_l.wallets.ongoing_compute_reps ();
  	});
  }
  
  void vban::wallets::split_if_needed (vban::transaction & transaction_destination, vban::block_store & store_a)
  {
  	auto store_l (dynamic_cast<vban::mdb_store *> (&store_a));
  	if (store_l != nullptr)
  	{
  		if (items.empty ())
  		{
  			std::string beginning (vban::uint256_union (0).to_string ());
  			std::string end ((vban::uint256_union (vban::uint256_t (0) - vban::uint256_t (1))).to_string ());
  
  			auto get_store_it = [&handle = handle] (vban::transaction const & transaction_source, std::string const & hash) {
  				return vban::store_iterator<std::array<char, 64>, vban::no_value> (std::make_unique<vban::mdb_iterator<std::array<char, 64>, vban::no_value>> (transaction_source, handle, vban::mdb_val (hash.size (), const_cast<char *> (hash.c_str ()))));
  			};
  
  			// First do a read pass to check if there are any wallets that need extracting (to save holding a write lock and potentially being blocked)
  			auto wallets_need_splitting (false);
  			{
  				auto transaction_source (store_l->tx_begin_read ());
  				auto i = get_store_it (transaction_source, beginning);
  				auto n = get_store_it (transaction_source, end);
  				wallets_need_splitting = (i != n);
  			}
  
  			if (wallets_need_splitting)
  			{
  				auto transaction_source (store_l->tx_begin_write ());
  				auto i = get_store_it (transaction_source, beginning);
  				auto n = get_store_it (transaction_source, end);
  				auto tx_source = static_cast<MDB_txn *> (transaction_source.get_handle ());
  				auto tx_destination = static_cast<MDB_txn *> (transaction_destination.get_handle ());
  				for (; i != n; ++i)
  				{
  					vban::uint256_union id;
  					std::string text (i->first.data (), i->first.size ());
  					auto error1 (id.decode_hex (text));
  					(void)error1;
  					debug_assert (!error1);
  					debug_assert (strlen (text.c_str ()) == text.size ());
  					move_table (text, tx_source, tx_destination);
  				}
  			}
  		}
  	}
  }
  
  void vban::wallets::move_table (std::string const & name_a, MDB_txn * tx_source, MDB_txn * tx_destination)
  {
  	MDB_dbi handle_source;
  	auto error2 (mdb_dbi_open (tx_source, name_a.c_str (), MDB_CREATE, &handle_source));
  	(void)error2;
  	debug_assert (!error2);
  	MDB_dbi handle_destination;
  	auto error3 (mdb_dbi_open (tx_destination, name_a.c_str (), MDB_CREATE, &handle_destination));
  	(void)error3;
  	debug_assert (!error3);
  	MDB_cursor * cursor;
  	auto error4 (mdb_cursor_open (tx_source, handle_source, &cursor));
  	(void)error4;
  	debug_assert (!error4);
  	MDB_val val_key;
  	MDB_val val_value;
  	auto cursor_status (mdb_cursor_get (cursor, &val_key, &val_value, MDB_FIRST));
  	while (cursor_status == MDB_SUCCESS)
  	{
  		auto error5 (mdb_put (tx_destination, handle_destination, &val_key, &val_value, 0));
  		(void)error5;
  		debug_assert (!error5);
  		cursor_status = mdb_cursor_get (cursor, &val_key, &val_value, MDB_NEXT);
  	}
  	auto error6 (mdb_drop (tx_source, handle_source, 1));
  	(void)error6;
  	debug_assert (!error6);
  }
  
  std::unordered_map<vban::wallet_id, std::shared_ptr<vban::wallet>> vban::wallets::get_wallets ()
  {
  	debug_assert (!mutex.try_lock ());
  	return items;
  }
  
  vban::uint256_t const vban::wallets::generate_priority = vban::uint256_t ("50000000000000000000000000000000000000");
  vban::uint256_t const vban::wallets::high_priority = vban::uint256_t ("50000000000000000000000000000000000000") - 1;
  
  vban::store_iterator<vban::account, vban::wallet_value> vban::wallet_store::begin (vban::transaction const & transaction_a)
  {
  	vban::store_iterator<vban::account, vban::wallet_value> result (std::make_unique<vban::mdb_iterator<vban::account, vban::wallet_value>> (transaction_a, handle, vban::mdb_val (vban::account (special_count))));
  	return result;
  }
  
  vban::store_iterator<vban::account, vban::wallet_value> vban::wallet_store::begin (vban::transaction const & transaction_a, vban::account const & key)
  {
  	vban::store_iterator<vban::account, vban::wallet_value> result (std::make_unique<vban::mdb_iterator<vban::account, vban::wallet_value>> (transaction_a, handle, vban::mdb_val (key)));
  	return result;
  }
  
  vban::store_iterator<vban::account, vban::wallet_value> vban::wallet_store::find (vban::transaction const & transaction_a, vban::account const & key)
  {
  	auto result (begin (transaction_a, key));
  	vban::store_iterator<vban::account, vban::wallet_value> end (nullptr);
  	if (result != end)
  	{
  		if (result->first == key)
  		{
  			return result;
  		}
  		else
  		{
  			return end;
  		}
  	}
  	else
  	{
  		return end;
  	}
  	return result;
  }
  
  vban::store_iterator<vban::account, vban::wallet_value> vban::wallet_store::end ()
  {
  	return vban::store_iterator<vban::account, vban::wallet_value> (nullptr);
  }
  vban::mdb_wallets_store::mdb_wallets_store (boost::filesystem::path const & path_a, vban::lmdb_config const & lmdb_config_a) :
  	environment (error, path_a, vban::mdb_env::options::make ().set_config (lmdb_config_a).override_config_sync (vban::lmdb_config::sync_strategy::always).override_config_map_size (1ULL * 1024 * 1024 * 1024))
  {
  }
  
  bool vban::mdb_wallets_store::init_error () const
  {
  	return error;
  }
  
  MDB_txn * vban::wallet_store::tx (vban::transaction const & transaction_a) const
  {
  	return static_cast<MDB_txn *> (transaction_a.get_handle ());
  }
  
  std::unique_ptr<vban::container_info_component> vban::collect_container_info (wallets & wallets, std::string const & name)
  {
  	size_t items_count;
  	size_t actions_count;
  	{
  		vban::lock_guard<vban::mutex> guard (wallets.mutex);
  		items_count = wallets.items.size ();
  		actions_count = wallets.actions.size ();
  	}
  
  	auto sizeof_item_element = sizeof (decltype (wallets.items)::value_type);
  	auto sizeof_actions_element = sizeof (decltype (wallets.actions)::value_type);
  	auto composite = std::make_unique<container_info_composite> (name);
  	composite->add_component (std::make_unique<container_info_leaf> (container_info{ "items", items_count, sizeof_item_element }));
  	composite->add_component (std::make_unique<container_info_leaf> (container_info{ "actions", actions_count, sizeof_actions_element }));
  	return composite;
  }
  