Reset Orchard wallet state when rescanning from below NU5 activation.

This commit is contained in:
Kris Nuttycombe 2022-03-02 19:50:14 -07:00
parent b78c2732ab
commit 6b8ecc8a31
4 changed files with 46 additions and 9 deletions

View File

@ -30,6 +30,15 @@ OrchardWalletPtr* orchard_wallet_new();
*/ */
void orchard_wallet_free(OrchardWalletPtr* wallet); void orchard_wallet_free(OrchardWalletPtr* wallet);
/**
* Reset the state of the wallet to be suitable for rescan from the NU5 activation
* height. This removes all witness and spentness information from the wallet. The
* keystore is unmodified and decrypted note, nullifier, and conflict data are left
* in place with the expectation that they will be overwritten and/or updated in
* the rescan process.
*/
bool orchard_wallet_reset(OrchardWalletPtr* wallet);
/** /**
* Adds a checkpoint to the wallet's note commitment tree to enable * Adds a checkpoint to the wallet's note commitment tree to enable
* a future rewind. * a future rewind.

View File

@ -487,6 +487,12 @@ pub extern "C" fn orchard_wallet_free(wallet: *mut Wallet) {
} }
} }
#[no_mangle]
pub extern "C" fn orchard_wallet_reset(wallet: *mut Wallet) {
let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null");
wallet.reset();
}
#[no_mangle] #[no_mangle]
pub extern "C" fn orchard_wallet_checkpoint(wallet: *mut Wallet, block_height: u32) -> bool { pub extern "C" fn orchard_wallet_checkpoint(wallet: *mut Wallet, block_height: u32) -> bool {
let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null"); let wallet = unsafe { wallet.as_mut() }.expect("Wallet pointer may not be null");

View File

@ -66,19 +66,27 @@ public:
OrchardWallet& operator=(const OrchardWallet&) = delete; OrchardWallet& operator=(const OrchardWallet&) = delete;
/** /**
* Checkpoint the note commitment tree. This returns `false` and leaves the * Reset the state of the wallet to be suitable for rescan from the NU5 activation
* note commitment tree unmodified if the block height does not match the * height. This removes all witness and spentness information from the wallet. The
* last block height scanned for transactions. This must be called exactly * keystore is unmodified and decrypted note, nullifier, and conflict data are left
* once per block. * in place with the expectation that they will be overwritten and/or updated in the
* rescan process.
*/
bool Reset() {
return orchard_wallet_reset(inner.get());
}
/**
* Checkpoint the note commitment tree. This returns `false` and leaves the note
* commitment tree unmodified if the block height does not match the last block
* height scanned for transactions. This must be called exactly once per block.
*/ */
bool CheckpointNoteCommitmentTree(int nBlockHeight) { bool CheckpointNoteCommitmentTree(int nBlockHeight) {
return orchard_wallet_checkpoint(inner.get(), (uint32_t) nBlockHeight); return orchard_wallet_checkpoint(inner.get(), (uint32_t) nBlockHeight);
} }
/** /**
* Rewind to the most recent checkpoint, and mark as unspent any notes * Return whether the orchard note commitment tree contains any checkpoints.
* previously identified as having been spent by transactions in the
* latest block.
*/ */
bool IsCheckpointed() const { bool IsCheckpointed() const {
return orchard_wallet_is_checkpointed(inner.get()); return orchard_wallet_is_checkpointed(inner.get());

View File

@ -3937,11 +3937,11 @@ int CWallet::ScanForWalletTransactions(
// checkpoints. Note data will be restored by the calls to AddToWalletIfInvolvingMe, // checkpoints. Note data will be restored by the calls to AddToWalletIfInvolvingMe,
// and then the call to `ChainTipAdded` that later occurs for each block will restore // and then the call to `ChainTipAdded` that later occurs for each block will restore
// the witness data that is being removed in the rewind here. // the witness data that is being removed in the rewind here.
auto nu5_height = chainParams.GetConsensus().GetActivationHeight(Consensus::UPGRADE_NU5);
bool performOrchardWalletUpdates{false}; bool performOrchardWalletUpdates{false};
if (orchardWallet.IsCheckpointed()) { if (orchardWallet.IsCheckpointed()) {
// We have a checkpoint, so attempt to rewind the Orchard wallet at most as // We have a checkpoint, so attempt to rewind the Orchard wallet at most as
// far as the NU5 activation block. // far as the NU5 activation block.
auto nu5_height = chainParams.GetConsensus().GetActivationHeight(Consensus::UPGRADE_NU5);
// If there's no activation height, we shouldn't have a checkpoint already, // If there's no activation height, we shouldn't have a checkpoint already,
// and this is a programming error. // and this is a programming error.
assert(nu5_height.has_value()); assert(nu5_height.has_value());
@ -3962,6 +3962,11 @@ int CWallet::ScanForWalletTransactions(
throw std::runtime_error("CWallet::ScanForWalletTransactions(): Orchard wallet is out of sync. Please restart your node with -rescan."); throw std::runtime_error("CWallet::ScanForWalletTransactions(): Orchard wallet is out of sync. Please restart your node with -rescan.");
} }
} }
} else if (isInitScan && pindex->nHeight < nu5_height) {
// If it's the initial scan and we're starting below the nu5 activation
// height, we're effectively rescanning from genesis and so it's safe
// to update the note commitment tree as we progress.
performOrchardWalletUpdates = true;
} }
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
@ -6015,8 +6020,17 @@ bool CWallet::InitLoadWallet(const CChainParams& params, bool clearWitnessCaches
return UIError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); return UIError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"));
} }
// If a rescan would begin at a point before NU5 activation height, reset
// the Orchard wallet state to empty.
if (pindexRescan->nHeight <= Params().GetConsensus().GetActivationHeight(Consensus::UPGRADE_NU5)) {
walletInstance->orchardWallet.Reset();
}
uiInterface.InitMessage(_("Rescanning...")); uiInterface.InitMessage(_("Rescanning..."));
LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); LogPrintf(
"Rescanning last %i blocks (from block %i)...\n",
chainActive.Height() - pindexRescan->nHeight,
pindexRescan->nHeight);
nStart = GetTimeMillis(); nStart = GetTimeMillis();
walletInstance->ScanForWalletTransactions(pindexRescan, true, true); walletInstance->ScanForWalletTransactions(pindexRescan, true, true);
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);