Support partial rewind of outputs for locked wallets, and UpgradeCoins() function to finish rewinding once unlocked.
This commit is contained in:
parent
c59ac6d8a5
commit
63147da911
@ -22,6 +22,8 @@ public:
|
||||
secret_key_t(std::array<uint8_t, NUM_BYTES>&& bytes) : m_value(BigInt<NUM_BYTES>(std::move(bytes))) { }
|
||||
secret_key_t(const uint8_t* bytes) : m_value(BigInt<NUM_BYTES>(bytes)) { }
|
||||
|
||||
static secret_key_t Null() { return secret_key_t<NUM_BYTES>(); }
|
||||
|
||||
static secret_key_t Random()
|
||||
{
|
||||
secret_key_t<NUM_BYTES> key;
|
||||
|
||||
@ -30,14 +30,14 @@ static constexpr uint32_t UNKNOWN_INDEX{std::numeric_limits<uint32_t>::max()};
|
||||
/// </summary>
|
||||
struct Coin : public Traits::ISerializable {
|
||||
// Version byte to more easily support adding new fields to the object.
|
||||
uint8_t version{1};
|
||||
uint8_t version{2};
|
||||
|
||||
// Index of the subaddress this coin was received at.
|
||||
uint32_t address_index{UNKNOWN_INDEX};
|
||||
|
||||
// The private key needed in order to spend the coin.
|
||||
// May be empty for watch-only wallets.
|
||||
boost::optional<SecretKey> key;
|
||||
boost::optional<SecretKey> spend_key;
|
||||
|
||||
// The blinding factor of the coin's output.
|
||||
// May be empty for watch-only wallets.
|
||||
@ -58,16 +58,37 @@ struct Coin : public Traits::ISerializable {
|
||||
// This will only be populated when the coin has flag HAS_SENDER_INFO.
|
||||
boost::optional<StealthAddress> address;
|
||||
|
||||
// The shared secret used to generate the output key.
|
||||
// By storing this, we are able to postpone calculation of the spend key.
|
||||
// This allows us to scan for outputs while wallet is locked, and recalculate
|
||||
// the output key once the wallet becomes unlocked.
|
||||
boost::optional<SecretKey> shared_secret;
|
||||
|
||||
bool IsChange() const noexcept { return address_index == CHANGE_INDEX; }
|
||||
bool IsPegIn() const noexcept { return address_index == PEGIN_INDEX; }
|
||||
bool IsMine() const noexcept { return address_index != UNKNOWN_INDEX; }
|
||||
bool HasAddress() const noexcept { return !!address; }
|
||||
bool HasSpendKey() const noexcept { return !!spend_key; }
|
||||
bool HasSharedSecret() const noexcept { return !!shared_secret; }
|
||||
|
||||
void Reset()
|
||||
{
|
||||
version = 2;
|
||||
address_index = UNKNOWN_INDEX;
|
||||
spend_key = boost::none;
|
||||
blind = boost::none;
|
||||
amount = 0;
|
||||
output_id = mw::Hash();
|
||||
sender_key = boost::none;
|
||||
address = boost::none;
|
||||
shared_secret = boost::none;
|
||||
}
|
||||
|
||||
IMPL_SERIALIZABLE(Coin, obj)
|
||||
{
|
||||
READWRITE(obj.version);
|
||||
READWRITE(VARINT(obj.address_index));
|
||||
READWRITE(obj.key);
|
||||
READWRITE(obj.spend_key);
|
||||
READWRITE(obj.blind);
|
||||
READWRITE(VARINT_MODE(obj.amount, VarIntMode::NONNEGATIVE_SIGNED));
|
||||
READWRITE(obj.output_id);
|
||||
@ -76,6 +97,10 @@ struct Coin : public Traits::ISerializable {
|
||||
READWRITE(obj.sender_key);
|
||||
READWRITE(obj.address);
|
||||
}
|
||||
|
||||
if (obj.version >= 2) {
|
||||
READWRITE(obj.shared_secret);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -21,13 +21,32 @@ public:
|
||||
m_scanSecret(std::move(scan_secret)),
|
||||
m_spendSecret(std::move(spend_secret)) { }
|
||||
|
||||
// If keychain is locked or watch-only (m_spendSecret is null),
|
||||
// this will still identify outputs belonging to the wallet, but
|
||||
// will not be able to calculate the coin's output key.
|
||||
// It will still calculate the shared_secret though, which can be
|
||||
// used to calculate the spend key when the wallet becomes unlocked.
|
||||
bool RewindOutput(const Output& output, mw::Coin& coin) const;
|
||||
|
||||
// Calculates the output spend key for the address index and shared secret.
|
||||
// Requires that keychain be unlocked and not watch-only.
|
||||
SecretKey CalculateOutputKey(const uint32_t index, const SecretKey& shared_secret) const;
|
||||
|
||||
// Calculates the StealthAddress at the given index.
|
||||
// Requires that keychain be unlocked and not watch-only.
|
||||
StealthAddress GetStealthAddress(const uint32_t index) const;
|
||||
|
||||
// Requires that keychain be unlocked and not watch-only.
|
||||
SecretKey GetSpendKey(const uint32_t index) const;
|
||||
|
||||
const SecretKey& GetScanSecret() const noexcept { return m_scanSecret; }
|
||||
const SecretKey& GetSpendSecret() const noexcept { return m_spendSecret; }
|
||||
|
||||
// Clears the spend secret from memory, effectively making this a watch-only keychain.
|
||||
void Lock() { m_spendSecret = SecretKey::Null(); }
|
||||
|
||||
// Reassigns the spend secret. To be used when unlocking the wallet.
|
||||
void Unlock(const SecretKey& spend_secret) { m_spendSecret = spend_secret; }
|
||||
|
||||
private:
|
||||
const ScriptPubKeyMan& m_spk_man;
|
||||
|
||||
@ -12,6 +12,7 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(!GetScanSecret().IsNull());
|
||||
PublicKey shared_secret = output.Ke().Mul(GetScanSecret());
|
||||
uint8_t view_tag = Hashed(EHashTag::TAG, shared_secret)[0];
|
||||
if (view_tag != output.GetViewTag()) {
|
||||
@ -40,33 +41,44 @@ bool Keychain::RewindOutput(const Output& output, mw::Coin& coin) const
|
||||
}
|
||||
|
||||
// Calculate Carol's sending key 's' and check that s*B ?= Ke
|
||||
StealthAddress wallet_addr = GetStealthAddress(index);
|
||||
SecretKey s = Hasher(EHashTag::SEND_KEY)
|
||||
.Append(wallet_addr.A())
|
||||
.Append(wallet_addr.B())
|
||||
.Append(address.A())
|
||||
.Append(address.B())
|
||||
.Append(value)
|
||||
.Append(n)
|
||||
.hash();
|
||||
if (output.Ke() != wallet_addr.B().Mul(s)) {
|
||||
if (output.Ke() != address.B().Mul(s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SecretKey private_key = SecretKeys::From(GetSpendKey(index))
|
||||
.Mul(Hashed(EHashTag::OUT_KEY, t))
|
||||
.Total();
|
||||
// Spend secret will be null for locked or watch-only wallets.
|
||||
if (!GetSpendSecret().IsNull()) {
|
||||
coin.spend_key = boost::make_optional(CalculateOutputKey(index, t));
|
||||
}
|
||||
|
||||
coin.address_index = index;
|
||||
coin.key = boost::make_optional(std::move(private_key));
|
||||
coin.blind = boost::make_optional(mask.GetRawBlind());
|
||||
coin.amount = value;
|
||||
coin.output_id = output.GetOutputID();
|
||||
coin.address = wallet_addr;
|
||||
coin.address = address;
|
||||
coin.shared_secret = boost::make_optional(std::move(t));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SecretKey Keychain::CalculateOutputKey(const uint32_t index, const SecretKey& shared_secret) const
|
||||
{
|
||||
assert(!m_spendSecret.IsNull());
|
||||
|
||||
return SecretKeys::From(GetSpendKey(index))
|
||||
.Mul(Hashed(EHashTag::OUT_KEY, shared_secret))
|
||||
.Total();
|
||||
}
|
||||
|
||||
StealthAddress Keychain::GetStealthAddress(const uint32_t index) const
|
||||
{
|
||||
assert(!m_spendSecret.IsNull());
|
||||
|
||||
PublicKey Bi = PublicKey::From(GetSpendKey(index));
|
||||
PublicKey Ai = Bi.Mul(m_scanSecret);
|
||||
|
||||
@ -75,6 +87,8 @@ StealthAddress Keychain::GetStealthAddress(const uint32_t index) const
|
||||
|
||||
SecretKey Keychain::GetSpendKey(const uint32_t index) const
|
||||
{
|
||||
assert(!m_spendSecret.IsNull());
|
||||
|
||||
SecretKey mi = Hasher(EHashTag::ADDRESS)
|
||||
.Append<uint32_t>(index)
|
||||
.Append(m_scanSecret)
|
||||
|
||||
@ -91,7 +91,7 @@ TxBuilder::Inputs TxBuilder::CreateInputs(const std::vector<mw::Coin>& input_coi
|
||||
input_coins.cbegin(), input_coins.cend(), std::back_inserter(inputs),
|
||||
[&blinds, &keys](const mw::Coin& input_coin) {
|
||||
assert(!!input_coin.blind);
|
||||
assert(!!input_coin.key);
|
||||
assert(!!input_coin.spend_key);
|
||||
|
||||
BlindingFactor blind = Pedersen::BlindSwitch(input_coin.blind.value(), input_coin.amount);
|
||||
SecretKey ephemeral_key = SecretKey::Random();
|
||||
@ -99,12 +99,12 @@ TxBuilder::Inputs TxBuilder::CreateInputs(const std::vector<mw::Coin>& input_coi
|
||||
input_coin.output_id,
|
||||
Commitment::Blinded(blind, input_coin.amount),
|
||||
ephemeral_key,
|
||||
input_coin.key.value()
|
||||
input_coin.spend_key.value()
|
||||
);
|
||||
|
||||
blinds.Add(blind);
|
||||
keys.Add(ephemeral_key);
|
||||
keys.Sub(input_coin.key.value());
|
||||
keys.Sub(input_coin.spend_key.value());
|
||||
return input;
|
||||
}
|
||||
);
|
||||
|
||||
@ -5,13 +5,43 @@
|
||||
|
||||
using namespace MWEB;
|
||||
|
||||
bool Wallet::UpgradeCoins()
|
||||
{
|
||||
mw::Keychain::Ptr keychain = GetKeychain();
|
||||
if (!keychain || keychain->GetSpendSecret().IsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through transactions and try upgrading output coins
|
||||
for (auto& entry : m_pWallet->mapWallet) {
|
||||
CWalletTx* wtx = &entry.second;
|
||||
RewindOutputs(*wtx->tx);
|
||||
|
||||
if (wtx->mweb_wtx_info && wtx->mweb_wtx_info->received_coin) {
|
||||
mw::Coin& coin = *wtx->mweb_wtx_info->received_coin;
|
||||
if (!coin.HasSpendKey() && coin.HasSharedSecret()) {
|
||||
coin.spend_key = keychain->CalculateOutputKey(coin.address_index, *coin.shared_secret);
|
||||
|
||||
m_coins[coin.output_id] = coin;
|
||||
|
||||
WalletBatch batch(m_pWallet->GetDatabase());
|
||||
batch.WriteMWEBCoin(coin);
|
||||
batch.WriteTx(*wtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<mw::Coin> Wallet::RewindOutputs(const CTransaction& tx)
|
||||
{
|
||||
std::vector<mw::Coin> coins;
|
||||
for (const CTxOutput& txout : tx.GetOutputs()) {
|
||||
if (txout.IsMWEB()) {
|
||||
|
||||
if (tx.HasMWEBTx()) {
|
||||
for (const Output& output : tx.mweb_tx.m_transaction->GetOutputs()) {
|
||||
mw::Coin mweb_coin;
|
||||
if (RewindOutput(tx.mweb_tx.m_transaction, txout.ToMWEB(), mweb_coin)) {
|
||||
if (RewindOutput(output, mweb_coin)) {
|
||||
coins.push_back(mweb_coin);
|
||||
}
|
||||
}
|
||||
@ -20,43 +50,25 @@ std::vector<mw::Coin> Wallet::RewindOutputs(const CTransaction& tx)
|
||||
return coins;
|
||||
}
|
||||
|
||||
bool Wallet::RewindOutput(const boost::variant<mw::Block::CPtr, mw::Transaction::CPtr>& parent,
|
||||
const mw::Hash& output_id, mw::Coin& coin)
|
||||
bool Wallet::RewindOutput(const Output& output, mw::Coin& coin)
|
||||
{
|
||||
if (GetCoin(output_id, coin) && coin.IsMine()) {
|
||||
return true;
|
||||
mw::Keychain::Ptr keychain = GetKeychain();
|
||||
|
||||
if (GetCoin(output.GetOutputID(), coin) && coin.IsMine()) {
|
||||
// If the coin has the spend key, it's fully rewound.
|
||||
// If not, try rewinding further if we have the master spend key (i.e. wallet is unlocked).
|
||||
if (coin.HasSpendKey() || !keychain || keychain->GetSpendSecret().IsNull()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
mw::Keychain::Ptr keychain = GetKeychain();
|
||||
if (!keychain) {
|
||||
if (!keychain || !keychain->RewindOutput(output, coin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rewound = false;
|
||||
if (parent.type() == typeid(mw::Block::CPtr)) {
|
||||
const mw::Block::CPtr& block = boost::get<mw::Block::CPtr>(parent);
|
||||
for (const Output& output : block->GetOutputs()) {
|
||||
if (output.GetOutputID() == output_id) {
|
||||
rewound = keychain->RewindOutput(output, coin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const mw::Transaction::CPtr& tx = boost::get<mw::Transaction::CPtr>(parent);
|
||||
for (const Output& output : tx->GetOutputs()) {
|
||||
if (output.GetOutputID() == output_id) {
|
||||
rewound = keychain->RewindOutput(output, coin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rewound) {
|
||||
m_coins[coin.output_id] = coin;
|
||||
WalletBatch(m_pWallet->GetDatabase()).WriteMWEBCoin(coin);
|
||||
}
|
||||
|
||||
return rewound;
|
||||
m_coins[coin.output_id] = coin;
|
||||
WalletBatch(m_pWallet->GetDatabase()).WriteMWEBCoin(coin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::IsChange(const StealthAddress& address) const
|
||||
@ -108,6 +120,7 @@ bool Wallet::GetCoin(const mw::Hash& output_id, mw::Coin& coin) const
|
||||
return true;
|
||||
}
|
||||
|
||||
coin.Reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -27,16 +27,17 @@ public:
|
||||
Wallet(CWallet* pWallet)
|
||||
: m_pWallet(pWallet) {}
|
||||
|
||||
bool IsSupported() const { return GetKeychain() != nullptr; }
|
||||
bool IsChange(const StealthAddress& address) const;
|
||||
bool GetCoin(const mw::Hash& output_id, mw::Coin& coin) const;
|
||||
|
||||
// Loops through the transactions in the wallet and attempts to fill
|
||||
// in missing information for mw::Coin's, in particular the spend key.
|
||||
// Intended to be called after unlocking an encrypted wallet.
|
||||
bool UpgradeCoins();
|
||||
|
||||
std::vector<mw::Coin> RewindOutputs(const CTransaction& tx);
|
||||
bool RewindOutput(
|
||||
const boost::variant<mw::Block::CPtr, mw::Transaction::CPtr>& parent,
|
||||
const mw::Hash& output_id,
|
||||
mw::Coin& coin
|
||||
);
|
||||
bool RewindOutput(const Output& output, mw::Coin& coin);
|
||||
|
||||
bool GetStealthAddress(const mw::Coin& coin, StealthAddress& address) const;
|
||||
bool GetStealthAddress(const uint32_t index, StealthAddress& address) const;
|
||||
|
||||
|
||||
@ -894,7 +894,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c
|
||||
if (Func("mweb", expr)) {
|
||||
auto pubkey = ParsePubkey(key_exp_index, expr, false, out, error);
|
||||
if (!pubkey) return nullptr;
|
||||
return MakeUnique<MWEBDescriptor>(std::move(pubkey), SecretKey()); // MW: TODO - Lookup scan_secret
|
||||
return MakeUnique<MWEBDescriptor>(std::move(pubkey), SecretKey::Null()); // MW: TODO - Lookup scan_secret
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
|
||||
auto pubkey = ParsePubkey(key_exp_index, expr, true, out, error);
|
||||
@ -1137,7 +1137,7 @@ std::unique_ptr<Descriptor> InferDescriptor(const DestinationAddr& script, const
|
||||
if (script.IsMWEB()) {
|
||||
// MW: TODO - Lookup scan_secret
|
||||
CPubKey pubkey(script.GetMWEBAddress().GetSpendPubKey().vec());
|
||||
return MakeUnique<MWEBDescriptor>(InferPubkey(pubkey, ParseScriptContext::TOP, provider), SecretKey());
|
||||
return MakeUnique<MWEBDescriptor>(InferPubkey(pubkey, ParseScriptContext::TOP, provider), SecretKey::Null());
|
||||
}
|
||||
|
||||
return InferScript(script.GetScript(), ParseScriptContext::TOP, provider);
|
||||
|
||||
@ -182,7 +182,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
|
||||
assert(v_solutions_ret_tx_multisig[2].size() == 1);
|
||||
|
||||
OutputType output_type{};
|
||||
const CTxDestination tx_destination = GetDestinationForKey(pubkey, output_type, SecretKey());
|
||||
const CTxDestination tx_destination = GetDestinationForKey(pubkey, output_type, SecretKey::Null());
|
||||
assert(output_type == OutputType::LEGACY);
|
||||
assert(IsValidDestination(tx_destination));
|
||||
assert(CTxDestination{PKHash{pubkey}} == tx_destination);
|
||||
|
||||
@ -376,7 +376,7 @@ RPCHelpMan importprunedfunds()
|
||||
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, merkleBlock.header.GetHash(), txnIndex);
|
||||
|
||||
CTransactionRef tx_ref = MakeTransactionRef(tx);
|
||||
if (pwallet->IsMine(*tx_ref)) {
|
||||
if (pwallet->IsMine(*tx_ref, boost::none)) {
|
||||
pwallet->AddToWallet(std::move(tx_ref), boost::none, confirm);
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
@ -1718,20 +1718,28 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
|
||||
|
||||
void LegacyScriptPubKeyMan::SetInternal(bool internal) {}
|
||||
|
||||
bool LegacyScriptPubKeyMan::LoadMWEBKeychain()
|
||||
void LegacyScriptPubKeyMan::LoadMWEBKeychain()
|
||||
{
|
||||
if (!m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) || m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// try to get the seed
|
||||
CKey seed;
|
||||
if (!GetKey(m_hd_chain.seed_id, seed)) {
|
||||
return false;
|
||||
if (m_hd_chain.mweb_scan_key) {
|
||||
m_mwebKeychain = std::make_shared<mw::Keychain>(
|
||||
*this,
|
||||
*m_hd_chain.mweb_scan_key,
|
||||
SecretKey::Null()
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CExtKey masterKey;
|
||||
@ -1751,12 +1759,22 @@ bool LegacyScriptPubKeyMan::LoadMWEBKeychain()
|
||||
CExtKey spendKey;
|
||||
chainChildKey.Derive(spendKey, BIP32_HARDENED_KEY_LIMIT + 1);
|
||||
|
||||
m_hd_chain.nVersion = std::max(m_hd_chain.nVersion, CHDChain::VERSION_HD_MWEB_WATCH);
|
||||
m_mwebKeychain = std::make_shared<mw::Keychain>(
|
||||
*this,
|
||||
SecretKey(scanKey.key.begin()),
|
||||
SecretKey(spendKey.key.begin())
|
||||
);
|
||||
|
||||
// Add the MWEB scan key to the CHDChain
|
||||
if (!m_hd_chain.mweb_scan_key) {
|
||||
m_hd_chain.mweb_scan_key = SecretKey(scanKey.key.begin());
|
||||
|
||||
if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(m_hd_chain)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
|
||||
}
|
||||
}
|
||||
|
||||
// Mark change and peg-in addresses as used
|
||||
if (m_hd_chain.nMWEBIndexCounter == 0) {
|
||||
WalletBatch batch(m_storage.GetDatabase());
|
||||
@ -1767,8 +1785,6 @@ bool LegacyScriptPubKeyMan::LoadMWEBKeychain()
|
||||
// Generate PEGIN pubkey
|
||||
GenerateNewKey(batch, m_hd_chain, KeyPurpose::MWEB);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
|
||||
@ -2450,9 +2466,4 @@ const std::vector<DestinationAddr> DescriptorScriptPubKeyMan::GetScriptPubKeys()
|
||||
script_pub_keys.push_back(script_pub_key.first);
|
||||
}
|
||||
return script_pub_keys;
|
||||
}
|
||||
|
||||
bool DescriptorScriptPubKeyMan::LoadMWEBKeychain()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -259,8 +259,7 @@ public:
|
||||
virtual void SetInternal(bool internal) {}
|
||||
|
||||
// Creates an MWEB KeyChain from the appropriate keychain paths.
|
||||
virtual bool LoadMWEBKeychain() { return false; }
|
||||
void UnloadMWEBKeychain() { m_mwebKeychain.reset(); }
|
||||
virtual void LoadMWEBKeychain() { }
|
||||
const mw::Keychain::Ptr& GetMWEBKeychain() const noexcept { return m_mwebKeychain; }
|
||||
|
||||
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
|
||||
@ -520,8 +519,19 @@ public:
|
||||
|
||||
std::set<CKeyID> GetKeys() const override;
|
||||
|
||||
bool LoadMWEBKeychain() override;
|
||||
SecretKey GetScanSecret() const noexcept { return m_mwebKeychain ? m_mwebKeychain->GetScanSecret() : SecretKey(); }
|
||||
void LoadMWEBKeychain() override;
|
||||
SecretKey GetScanSecret() const noexcept
|
||||
{
|
||||
if (m_mwebKeychain) {
|
||||
return m_mwebKeychain->GetScanSecret();
|
||||
}
|
||||
|
||||
if (IsHDEnabled() && m_hd_chain.mweb_scan_key) {
|
||||
return *m_hd_chain.mweb_scan_key;
|
||||
}
|
||||
|
||||
return SecretKey::Null();
|
||||
};
|
||||
};
|
||||
|
||||
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */
|
||||
@ -642,8 +652,6 @@ public:
|
||||
|
||||
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
|
||||
const std::vector<DestinationAddr> GetScriptPubKeys() const;
|
||||
|
||||
bool LoadMWEBKeychain() override;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
|
||||
|
||||
@ -343,6 +343,15 @@ std::string COutput::ToString() const
|
||||
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
|
||||
}
|
||||
|
||||
CWalletTx* CWallet::GetWalletTx(const uint256& hash)
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
std::map<uint256, CWalletTx>::iterator it = mapWallet.find(hash);
|
||||
if (it == mapWallet.end())
|
||||
return nullptr;
|
||||
return &(it->second);
|
||||
}
|
||||
|
||||
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
@ -388,6 +397,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_key
|
||||
auto mweb_spk_man = GetScriptPubKeyMan(OutputType::MWEB, false);
|
||||
if (mweb_spk_man) {
|
||||
mweb_spk_man->LoadMWEBKeychain();
|
||||
mweb_wallet->UpgradeCoins();
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -605,6 +615,10 @@ void CWallet::AddMWEBOrigins(const CWalletTx& wtx)
|
||||
const mw::Hash& output_id = wtx.mweb_wtx_info->received_coin->output_id;
|
||||
mapOutputsMWEB.insert(std::make_pair(output_id, wtx.GetHash()));
|
||||
}
|
||||
|
||||
for (const mw::Hash& kernel_id : wtx.tx->mweb_tx.GetKernelIDs()) {
|
||||
mapKernelsMWEB.insert(std::make_pair(kernel_id, wtx.GetHash()));
|
||||
}
|
||||
}
|
||||
|
||||
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
||||
@ -1015,18 +1029,21 @@ bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate)
|
||||
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, CWalletTx::Confirmation confirm, bool fUpdate)
|
||||
{
|
||||
CWalletTx wtx(this, ptx, mweb_wtx_info);
|
||||
uint256 hash = wtx.GetHash();
|
||||
|
||||
const CTransaction& tx = *ptx;
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
|
||||
if (!confirm.hashBlock.IsNull()) {
|
||||
for (const CTxInput& txin : tx.GetInputs()) {
|
||||
for (const CTxInput& txin : wtx.GetInputs()) {
|
||||
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.GetIndex());
|
||||
while (range.first != range.second) {
|
||||
if (range.first->second != tx.GetHash()) {
|
||||
WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString());
|
||||
if (range.first->second != hash) {
|
||||
WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s\n", hash.ToString(), confirm.hashBlock.ToString(), range.first->second.ToString());
|
||||
MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second);
|
||||
}
|
||||
range.first++;
|
||||
@ -1036,9 +1053,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co
|
||||
|
||||
mweb_wallet->RewindOutputs(tx);
|
||||
|
||||
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
|
||||
bool fExisted = mapWallet.count(hash) != 0;
|
||||
if (fExisted && !fUpdate) return false;
|
||||
if (fExisted || IsMine(tx) || IsFromMe(tx, boost::none))
|
||||
if (fExisted || IsMine(tx, mweb_wtx_info) || IsFromMe(tx, mweb_wtx_info))
|
||||
{
|
||||
/* Check if any keys in the wallet keypool that were supposed to be unused
|
||||
* have appeared in a new transaction. If so, remove those keys from the keypool.
|
||||
@ -1047,7 +1064,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co
|
||||
*/
|
||||
|
||||
// loop though all outputs
|
||||
for (const CTxOutput& txout : tx.GetOutputs()) {
|
||||
for (const CTxOutput& txout : wtx.GetOutputs()) {
|
||||
DestinationAddr dest;
|
||||
if (ExtractDestinationScript(txout, dest)) {
|
||||
for (const auto& spk_man_pair : m_spk_managers) {
|
||||
@ -1065,7 +1082,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co
|
||||
|
||||
// Block disconnection override an abandoned tx as unconfirmed
|
||||
// which means user may have to call abandontransaction again
|
||||
return AddToWallet(MakeTransactionRef(tx), /* mweb_wtx_info */ boost::none, confirm, /* update_wtx= */ nullptr, /* fFlushOnClose= */ false);
|
||||
return AddToWallet(MakeTransactionRef(tx), mweb_wtx_info, confirm, /* update_wtx= */ nullptr, /* fFlushOnClose= */ false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -1078,9 +1095,9 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
|
||||
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
|
||||
}
|
||||
|
||||
void CWallet::MarkInputsDirty(const CTransactionRef& tx)
|
||||
void CWallet::MarkInputsDirty(const CWalletTx& wtx)
|
||||
{
|
||||
for (const CTxInput& txin : tx->GetInputs()) {
|
||||
for (const CTxInput& txin : wtx.GetInputs()) {
|
||||
CWalletTx* prev = FindPrevTx(txin);
|
||||
if (prev != nullptr) {
|
||||
prev->MarkDirty();
|
||||
@ -1136,7 +1153,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
|
||||
}
|
||||
// If a transaction changes 'conflicted' state, that changes the balance
|
||||
// available of the outputs it spends. So force those to be recomputed
|
||||
MarkInputsDirty(wtx.tx);
|
||||
MarkInputsDirty(wtx);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1191,25 +1208,25 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
|
||||
}
|
||||
// If a transaction changes 'conflicted' state, that changes the balance
|
||||
// available of the outputs it spends. So force those to be recomputed
|
||||
MarkInputsDirty(wtx.tx);
|
||||
MarkInputsDirty(wtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx)
|
||||
void CWallet::SyncTransaction(const CTransactionRef& ptx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, CWalletTx::Confirmation confirm, bool update_tx)
|
||||
{
|
||||
if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx))
|
||||
if (!AddToWalletIfInvolvingMe(ptx, mweb_wtx_info, confirm, update_tx))
|
||||
return; // Not one of ours
|
||||
|
||||
// If a transaction changes 'conflicted' state, that changes the balance
|
||||
// available of the outputs it spends. So force those to be
|
||||
// recomputed, also:
|
||||
MarkInputsDirty(ptx);
|
||||
MarkInputsDirty(CWalletTx(this, ptx, mweb_wtx_info));
|
||||
}
|
||||
|
||||
void CWallet::transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) {
|
||||
LOCK(cs_wallet);
|
||||
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
SyncTransaction(tx, boost::none, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
|
||||
auto it = mapWallet.find(tx->GetHash());
|
||||
if (it != mapWallet.end()) {
|
||||
@ -1223,6 +1240,17 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
|
||||
if (it != mapWallet.end()) {
|
||||
it->second.fInMempool = false;
|
||||
}
|
||||
|
||||
for (const mw::Hash& output_id : tx->mweb_tx.GetOutputIDs()) {
|
||||
auto out_iter = mapOutputsMWEB.find(output_id);
|
||||
if (out_iter != mapOutputsMWEB.end()) {
|
||||
auto tx_iter = mapWallet.find(out_iter->second);
|
||||
if (tx_iter != mapWallet.end()) {
|
||||
tx_iter->second.fInMempool = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle transactions that were removed from the mempool because they
|
||||
// conflict with transactions in a newly connected block.
|
||||
if (reason == MemPoolRemovalReason::CONFLICT) {
|
||||
@ -1251,7 +1279,7 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
|
||||
// distinguishing between conflicted and unconfirmed transactions are
|
||||
// imperfect, and could be improved in general, see
|
||||
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
|
||||
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
SyncTransaction(tx, boost::none, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1263,7 +1291,7 @@ void CWallet::blockConnected(const CBlock& block, int height)
|
||||
m_last_block_processed_height = height;
|
||||
m_last_block_processed = block_hash;
|
||||
for (size_t index = 0; index < block.vtx.size(); index++) {
|
||||
SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index});
|
||||
SyncTransaction(block.vtx[index], boost::none, {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index});
|
||||
transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
|
||||
}
|
||||
|
||||
@ -1279,12 +1307,28 @@ void CWallet::blockConnected(const CBlock& block, int height)
|
||||
}
|
||||
}
|
||||
|
||||
CWalletTx* hogex_wtx = GetWalletTx(block.vtx.back()->GetHash());
|
||||
if (hogex_wtx != nullptr) {
|
||||
hogex_wtx->pegout_indices.clear();
|
||||
hogex_wtx->pegout_indices.push_back({mw::Hash(), 0}); // HogAddr doesn't have a corresponding kernel
|
||||
|
||||
for (const Kernel& kernel : block.mweb_block.m_block->GetKernels()) {
|
||||
const auto& kernel_pegouts = kernel.GetPegOuts();
|
||||
for (size_t pegout_idx = 0; pegout_idx < kernel_pegouts.size(); pegout_idx++) {
|
||||
hogex_wtx->pegout_indices.push_back({kernel.GetKernelID(), pegout_idx});
|
||||
}
|
||||
}
|
||||
|
||||
assert(hogex_wtx->tx->vout.size() == hogex_wtx->pegout_indices.size());
|
||||
WalletBatch(*database).WriteTx(*hogex_wtx);
|
||||
}
|
||||
|
||||
mw::Coin mweb_coin;
|
||||
for (const mw::Hash& output_id : block.mweb_block.GetOutputIDs()) {
|
||||
if (mweb_wallet->RewindOutput(block.mweb_block.m_block, output_id, mweb_coin)) {
|
||||
auto wtx = FindWalletTx(output_id);
|
||||
for (const Output& output : block.mweb_block.m_block->GetOutputs()) {
|
||||
if (mweb_wallet->RewindOutput(output, mweb_coin)) {
|
||||
auto wtx = FindWalletTx(output.GetOutputID());
|
||||
if (wtx != nullptr) {
|
||||
SyncTransaction(wtx->tx, {CWalletTx::Status::CONFIRMED, height, block_hash, wtx->m_confirm.nIndex});
|
||||
SyncTransaction(wtx->tx, wtx->mweb_wtx_info, {CWalletTx::Status::CONFIRMED, height, block_hash, wtx->m_confirm.nIndex});
|
||||
transactionRemovedFromMempool(wtx->tx, MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
|
||||
} else {
|
||||
AddToWallet(
|
||||
@ -1309,7 +1353,7 @@ void CWallet::blockDisconnected(const CBlock& block, int height)
|
||||
m_last_block_processed_height = height - 1;
|
||||
m_last_block_processed = block.hashPrevBlock;
|
||||
for (const CTransactionRef& ptx : block.vtx) {
|
||||
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
SyncTransaction(ptx, boost::none, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
}
|
||||
|
||||
if (!block.mweb_block.IsNull()) {
|
||||
@ -1319,17 +1363,27 @@ void CWallet::blockDisconnected(const CBlock& block, int height)
|
||||
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(spent_id);
|
||||
// MWEB: We just choose the first spend. In the future, we may need a better approach for handling conflicted txs
|
||||
if (range.first != range.second) {
|
||||
auto ptx = mapWallet.find(range.first->second)->second.tx;
|
||||
SyncTransaction(ptx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
auto tx_iter = mapWallet.find(range.first->second);
|
||||
SyncTransaction(
|
||||
tx_iter->second.tx,
|
||||
tx_iter->second.mweb_wtx_info,
|
||||
{CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const mw::Hash& output_id : block.mweb_block.GetOutputIDs()) {
|
||||
if (mweb_wallet->RewindOutput(block.mweb_block.m_block, output_id, coin)) {
|
||||
auto wtx = FindWalletTx(output_id);
|
||||
// MW: TODO - Pegout kernels?
|
||||
|
||||
for (const Output& output : block.mweb_block.m_block->GetOutputs()) {
|
||||
if (mweb_wallet->RewindOutput(output, coin)) {
|
||||
auto wtx = FindWalletTx(output.GetOutputID());
|
||||
if (wtx != nullptr) {
|
||||
SyncTransaction(wtx->tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0});
|
||||
SyncTransaction(
|
||||
wtx->tx,
|
||||
wtx->mweb_wtx_info,
|
||||
{CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1484,7 +1538,7 @@ CAmount CWallet::GetChange(const CTxOutput& output) const
|
||||
return (IsChange(output) ? amount : 0);
|
||||
}
|
||||
|
||||
bool CWallet::IsMine(const CTransaction& tx) const
|
||||
bool CWallet::IsMine(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
for (const CTxOutput& txout : tx.GetOutputs())
|
||||
@ -1497,6 +1551,12 @@ bool CWallet::IsMine(const CTransaction& tx) const
|
||||
}
|
||||
}
|
||||
|
||||
if (mweb_wtx_info && mweb_wtx_info->received_coin) {
|
||||
if (mweb_wtx_info->received_coin->IsMine()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1971,17 +2031,17 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
|
||||
break;
|
||||
}
|
||||
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
||||
SyncTransaction(block.vtx[posInBlock], {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, fUpdate);
|
||||
SyncTransaction(block.vtx[posInBlock], boost::none, {CWalletTx::Status::CONFIRMED, block_height, block_hash, (int)posInBlock}, fUpdate);
|
||||
}
|
||||
|
||||
if (!block.mweb_block.IsNull()) {
|
||||
// MW: TODO - Pegouts?
|
||||
mw::Coin mweb_coin;
|
||||
for (const mw::Hash& output_id : block.mweb_block.GetOutputIDs()) {
|
||||
if (mweb_wallet->RewindOutput(block.mweb_block.m_block, output_id, mweb_coin)) {
|
||||
for (const Output& output : block.mweb_block.m_block->GetOutputs()) {
|
||||
if (mweb_wallet->RewindOutput(output, mweb_coin)) {
|
||||
const CWalletTx* wtx = FindWalletTx(mweb_coin.output_id);
|
||||
if (wtx) {
|
||||
// MW: TODO - Update height
|
||||
SyncTransaction(wtx->tx, wtx->mweb_wtx_info, {CWalletTx::Status::CONFIRMED, block_height, block_hash, wtx->m_confirm.nIndex}, fUpdate);
|
||||
} else {
|
||||
AddToWallet(
|
||||
MakeTransactionRef(),
|
||||
@ -1996,14 +2056,26 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
|
||||
|
||||
for (const mw::Hash& spent_id : block.mweb_block.GetSpentIDs()) {
|
||||
if (IsMine(CTxInput(spent_id))) {
|
||||
// MW: TODO - Check for zapped transactions with matching spent IDs
|
||||
AddToWallet(
|
||||
MakeTransactionRef(),
|
||||
boost::make_optional<MWEB::WalletTxInfo>(spent_id),
|
||||
{CWalletTx::Status::CONFIRMED, block_height, block_hash, 0},
|
||||
nullptr,
|
||||
false
|
||||
);
|
||||
auto spend_iter = mapTxSpends.find(spent_id);
|
||||
if (spend_iter != mapTxSpends.end()) {
|
||||
auto tx_iter = mapWallet.find(spend_iter->second);
|
||||
if (tx_iter != mapWallet.end()) {
|
||||
SyncTransaction(
|
||||
tx_iter->second.tx,
|
||||
tx_iter->second.mweb_wtx_info,
|
||||
{CWalletTx::Status::CONFIRMED, block_height, block_hash, 0},
|
||||
fUpdate
|
||||
);
|
||||
}
|
||||
} else {
|
||||
AddToWallet(
|
||||
MakeTransactionRef(),
|
||||
boost::make_optional<MWEB::WalletTxInfo>(spent_id),
|
||||
{CWalletTx::Status::CONFIRMED, block_height, block_hash, 0},
|
||||
nullptr,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
CWalletTx* prev = FindPrevTx(spent_id);
|
||||
if (prev != nullptr) {
|
||||
@ -2589,7 +2661,7 @@ std::map<CTxDestination, std::vector<COutputCoin>> CWallet::ListCoins() const
|
||||
if (ExtractOutputDestination(FindNonChangeParentOutput(*wtx->tx, output_idx), address)) {
|
||||
if (output_idx.type() == typeid(mw::Hash)) {
|
||||
mw::Coin coin;
|
||||
if (GetCoin(boost::get<mw::Hash>(output_idx), coin) && coin.IsMine()) {
|
||||
if (GetCoin(boost::get<mw::Hash>(output_idx), coin) && coin.IsMine() && coin.HasSpendKey()) {
|
||||
result[address].emplace_back(MWOutput{coin, depth, boost::get<StealthAddress>(address), wtx});
|
||||
}
|
||||
} else {
|
||||
@ -4073,7 +4145,10 @@ bool CWallet::Lock()
|
||||
// MWEB: Unload MWEB keychain
|
||||
auto mweb_spk_man = GetScriptPubKeyMan(OutputType::MWEB, false);
|
||||
if (mweb_spk_man) {
|
||||
mweb_spk_man->UnloadMWEBKeychain();
|
||||
const mw::Keychain::Ptr& keychain = mweb_spk_man->GetMWEBKeychain();
|
||||
if (keychain) {
|
||||
keychain->Lock();
|
||||
}
|
||||
}
|
||||
|
||||
NotifyStatusChanged(this);
|
||||
|
||||
@ -195,6 +195,41 @@ static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
|
||||
mapValue["n"] = ToString(nOrderPos);
|
||||
}
|
||||
|
||||
static inline void ReadPegoutIndices(std::vector<std::pair<mw::Hash, size_t>>& pegout_indices, const mapValue_t& mapValue)
|
||||
{
|
||||
if (!mapValue.count("pegout_indices")) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> bytes = ParseHex(mapValue.at("pegout_indices"));
|
||||
CDataStream s((const char*)bytes.data(), (const char*)bytes.data() + bytes.size(), SER_DISK, PROTOCOL_VERSION);
|
||||
|
||||
size_t num_indices = ReadVarInt<CDataStream, VarIntMode::DEFAULT, size_t>(s);
|
||||
for (size_t i = 0; i < num_indices; i++) {
|
||||
mw::Hash kernel_id;
|
||||
s >> kernel_id;
|
||||
size_t sub_idx = ReadVarInt<CDataStream, VarIntMode::DEFAULT, size_t>(s);
|
||||
|
||||
pegout_indices.push_back({std::move(kernel_id), sub_idx});
|
||||
}
|
||||
}
|
||||
|
||||
static inline void WritePegoutIndices(const std::vector<std::pair<mw::Hash, size_t>>& pegout_indices, mapValue_t& mapValue)
|
||||
{
|
||||
if (pegout_indices.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CDataStream s(SER_DISK, PROTOCOL_VERSION);
|
||||
WriteVarInt<CDataStream, VarIntMode::DEFAULT, size_t>(s, pegout_indices.size());
|
||||
for (const auto& pegout_idx : pegout_indices) {
|
||||
pegout_idx.first.Serialize(s);
|
||||
WriteVarInt<CDataStream, VarIntMode::DEFAULT, size_t>(s, pegout_idx.second);
|
||||
}
|
||||
|
||||
mapValue["pegout_indices"] = HexStr(std::vector<uint8_t>{s.begin(), s.end()});
|
||||
}
|
||||
|
||||
struct COutputEntry
|
||||
{
|
||||
CTxDestination destination;
|
||||
@ -289,6 +324,7 @@ public:
|
||||
std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered;
|
||||
|
||||
boost::optional<MWEB::WalletTxInfo> mweb_wtx_info;
|
||||
std::vector<std::pair<mw::Hash, size_t>> pegout_indices;
|
||||
|
||||
// memory only
|
||||
enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS };
|
||||
@ -371,6 +407,7 @@ public:
|
||||
mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart);
|
||||
}
|
||||
|
||||
WritePegoutIndices(pegout_indices, mapValueCopy);
|
||||
if (mweb_wtx_info) {
|
||||
mapValueCopy["mweb_info"] = mweb_wtx_info->ToHex();
|
||||
}
|
||||
@ -412,6 +449,8 @@ public:
|
||||
|
||||
ReadOrderPos(nOrderPos, mapValue);
|
||||
nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
|
||||
|
||||
ReadPegoutIndices(pegout_indices, mapValue);
|
||||
mweb_wtx_info = mapValue.count("mweb_info") ? boost::make_optional(MWEB::WalletTxInfo::FromHex(mapValue["mweb_info"])) : boost::none;
|
||||
|
||||
mapValue.erase("fromaccount");
|
||||
@ -419,6 +458,7 @@ public:
|
||||
mapValue.erase("n");
|
||||
mapValue.erase("timesmart");
|
||||
mapValue.erase("mweb_info");
|
||||
mapValue.erase("pegout_indices");
|
||||
}
|
||||
|
||||
void SetTx(CTransactionRef arg)
|
||||
@ -758,6 +798,10 @@ private:
|
||||
* Used to keep track of which CWalletTx an MWEB output came from.
|
||||
*/
|
||||
std::map<mw::Hash, uint256> mapOutputsMWEB GUARDED_BY(cs_wallet);
|
||||
/**
|
||||
* Used to keep track of which CWalletTx an MWEB kernel is in.
|
||||
*/
|
||||
std::map<mw::Hash, uint256> mapKernelsMWEB GUARDED_BY(cs_wallet); // MW: TODO - Could be multiple transactions. Need to handle conflicts?
|
||||
void AddMWEBOrigins(const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/**
|
||||
@ -773,19 +817,19 @@ private:
|
||||
* Abandoned state should probably be more carefully tracked via different
|
||||
* posInBlock signals or by checking mempool presence when necessary.
|
||||
*/
|
||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, CWalletTx::Confirmation confirm, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
|
||||
void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx);
|
||||
|
||||
/* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */
|
||||
void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void MarkInputsDirty(const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
|
||||
* Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */
|
||||
void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void SyncTransaction(const CTransactionRef& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, CWalletTx::Confirmation confirm, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
std::atomic<uint64_t> m_wallet_flags{0};
|
||||
|
||||
@ -904,6 +948,7 @@ public:
|
||||
/** Interface for accessing chain state. */
|
||||
interfaces::Chain& chain() const { assert(m_chain); return *m_chain; }
|
||||
|
||||
CWalletTx* GetWalletTx(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
@ -1162,7 +1207,7 @@ public:
|
||||
bool IsChange(const CTxOutput& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool IsChange(const DestinationAddr& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
CAmount GetChange(const CTxOutput& output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool IsMine(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
/** should probably be renamed to IsRelevantToMe */
|
||||
bool IsFromMe(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info) const;
|
||||
CAmount GetDebit(const CTransaction& tx, const boost::optional<MWEB::WalletTxInfo>& mweb_wtx_info, const isminefilter& filter) const;
|
||||
|
||||
@ -511,7 +511,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
}
|
||||
|
||||
if (!!keyMeta.mweb_index) {
|
||||
chain.nVersion = CHDChain::VERSION_HD_MWEB;
|
||||
chain.nVersion = std::max(chain.nVersion, CHDChain::VERSION_HD_MWEB_WATCH);
|
||||
chain.nMWEBIndexCounter = std::max(chain.nMWEBIndexCounter, *keyMeta.mweb_index);
|
||||
} else if (internal) {
|
||||
chain.nVersion = std::max(chain.nVersion, CHDChain::VERSION_HD_CHAIN_SPLIT);
|
||||
|
||||
@ -93,11 +93,13 @@ public:
|
||||
uint32_t nInternalChainCounter;
|
||||
uint32_t nMWEBIndexCounter;
|
||||
CKeyID seed_id; //!< seed hash160
|
||||
boost::optional<SecretKey> mweb_scan_key;
|
||||
|
||||
static const int VERSION_HD_BASE = 1;
|
||||
static const int VERSION_HD_CHAIN_SPLIT = 2;
|
||||
static const int VERSION_HD_MWEB = 3;
|
||||
static const int CURRENT_VERSION = VERSION_HD_MWEB;
|
||||
static const int VERSION_HD_MWEB_WATCH = 4;
|
||||
static const int CURRENT_VERSION = VERSION_HD_MWEB_WATCH;
|
||||
int nVersion;
|
||||
|
||||
CHDChain() { SetNull(); }
|
||||
@ -112,6 +114,10 @@ public:
|
||||
if (obj.nVersion >= VERSION_HD_MWEB) {
|
||||
READWRITE(obj.nMWEBIndexCounter);
|
||||
}
|
||||
|
||||
if (obj.nVersion >= VERSION_HD_MWEB_WATCH) {
|
||||
READWRITE(obj.mweb_scan_key);
|
||||
}
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
@ -121,6 +127,7 @@ public:
|
||||
nInternalChainCounter = 0;
|
||||
nMWEBIndexCounter = 0;
|
||||
seed_id.SetNull();
|
||||
mweb_scan_key = boost::none;
|
||||
}
|
||||
|
||||
bool operator==(const CHDChain& chain) const
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user