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);
/**
* 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
* 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]
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");

View File

@ -66,19 +66,27 @@ public:
OrchardWallet& operator=(const OrchardWallet&) = delete;
/**
* 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.
* 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 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) {
return orchard_wallet_checkpoint(inner.get(), (uint32_t) nBlockHeight);
}
/**
* Rewind to the most recent checkpoint, and mark as unspent any notes
* previously identified as having been spent by transactions in the
* latest block.
* Return whether the orchard note commitment tree contains any checkpoints.
*/
bool IsCheckpointed() const {
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,
// 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.
auto nu5_height = chainParams.GetConsensus().GetActivationHeight(Consensus::UPGRADE_NU5);
bool performOrchardWalletUpdates{false};
if (orchardWallet.IsCheckpointed()) {
// We have a checkpoint, so attempt to rewind the Orchard wallet at most as
// 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,
// and this is a programming error.
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.");
}
}
} 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
@ -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)"));
}
// 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..."));
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();
walletInstance->ScanForWalletTransactions(pindexRescan, true, true);
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);