Merge #16624: wallet: encapsulate transactions state
442a87cc0a
Add a test wallet_reorgsrestore (Antoine Riard)40ede992d9
Modify wallet tx status if has been reorged out (Antoine Riard)7e89994133
Remove SyncTransaction for conflicted txn in CWallet::BlockConnected (Antoine Riard)a31be09bfd
Encapsulate tx status in a Confirmation struct (Antoine Riard) Pull request description: While working on #15931, I've tried to rationalize tx state management to ease integration of block height tracking per-wallet tx. We currently rely on a combination of `hashBlock` and `nIndex` with magic value to determine tx confirmation, conflicted or abandoned state. It's hard to reason and error-prone. To solve that, we encapsulate these fields in a `TxConfirmation` struct and introduce a `TxState` member that we update accordingly at block connection/disconnection. Following jnewbery [recommendation](https://github.com/bitcoin/bitcoin/pull/15931#discussion_r312576506), I've taken these changes in its own commit, and open a PR to get them first. It would ease review of aforementioned PR, but above all should ease fixing of long-term issues like : * https://github.com/bitcoin/bitcoin/issues/7315 (but maybe we should abandon abandontransaction or relieve it to only free outpoints not track the transaction as abandoned in itself, need its own discussion) * https://github.com/bitcoin/bitcoin/issues/8692 where we should cancel conflicted state of transactions chain smoothly * `MarkConflicted` in `LoadToWallet` is likely useless if we track conflicts rights at block connection Main changes of this PR to get right are tx update in `AddToWallet` and serialization/deserialization logic. ACKs for top commit: meshcollider: Light re-Code Review ACK442a87cc0a
ryanofsky: utACK442a87cc0a
. Changes since last review are switching from `hasChain` to `LockChain` and removing chain lock in `WalletBatch::LoadWallet` that's redundant with the new lock still added in `CWallet::LoadWallet`, and fixing python test race condition. Tree-SHA512: 029209e006de0240436817204e69e548c5665e2b0721b214510e7aba7eba130a5eab441d3a1ad95bd6426114dd27390492c77bf4560a9610009b32cd0a1f72f7
This commit is contained in:
commit
5e202382a9
8 changed files with 261 additions and 87 deletions
|
@ -65,7 +65,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co
|
||||||
WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx)
|
WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx)
|
||||||
{
|
{
|
||||||
WalletTxStatus result;
|
WalletTxStatus result;
|
||||||
result.block_height = locked_chain.getBlockHeight(wtx.hashBlock).get_value_or(std::numeric_limits<int>::max());
|
result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max());
|
||||||
result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain);
|
result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain);
|
||||||
result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain);
|
result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain);
|
||||||
result.time_received = wtx.nTimeReceived;
|
result.time_received = wtx.nTimeReceived;
|
||||||
|
|
|
@ -384,8 +384,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
|
||||||
}
|
}
|
||||||
|
|
||||||
wtx.nIndex = txnIndex;
|
wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex);
|
||||||
wtx.hashBlock = merkleBlock.header.GetHash();
|
|
||||||
|
|
||||||
auto locked_chain = pwallet->chain().lock();
|
auto locked_chain = pwallet->chain().lock();
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
|
@ -134,10 +134,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
|
||||||
entry.pushKV("generated", true);
|
entry.pushKV("generated", true);
|
||||||
if (confirms > 0)
|
if (confirms > 0)
|
||||||
{
|
{
|
||||||
entry.pushKV("blockhash", wtx.hashBlock.GetHex());
|
entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
|
||||||
entry.pushKV("blockindex", wtx.nIndex);
|
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
|
||||||
int64_t block_time;
|
int64_t block_time;
|
||||||
bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time);
|
bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
|
||||||
assert(found_block);
|
assert(found_block);
|
||||||
entry.pushKV("blocktime", block_time);
|
entry.pushKV("blocktime", block_time);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -249,8 +249,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
|
||||||
LockAssertion lock(::cs_main);
|
LockAssertion lock(::cs_main);
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash();
|
wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0);
|
||||||
wtx.nIndex = 0;
|
|
||||||
|
|
||||||
// Call GetImmatureCredit() once before adding the key to the wallet to
|
// Call GetImmatureCredit() once before adding the key to the wallet to
|
||||||
// cache the current immature credit amount, which is 0.
|
// cache the current immature credit amount, which is 0.
|
||||||
|
@ -281,14 +280,19 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
|
||||||
}
|
}
|
||||||
|
|
||||||
CWalletTx wtx(&wallet, MakeTransactionRef(tx));
|
CWalletTx wtx(&wallet, MakeTransactionRef(tx));
|
||||||
if (block) {
|
|
||||||
wtx.SetMerkleBranch(block->GetBlockHash(), 0);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
|
LOCK(wallet.cs_wallet);
|
||||||
|
// If transaction is already in map, to avoid inconsistencies, unconfirmation
|
||||||
|
// is needed before confirm again with different block.
|
||||||
|
std::map<uint256, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetHash());
|
||||||
|
if (it != wallet.mapWallet.end()) {
|
||||||
|
wtx.setUnconfirmed();
|
||||||
wallet.AddToWallet(wtx);
|
wallet.AddToWallet(wtx);
|
||||||
}
|
}
|
||||||
LOCK(wallet.cs_wallet);
|
if (block) {
|
||||||
|
wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0);
|
||||||
|
}
|
||||||
|
wallet.AddToWallet(wtx);
|
||||||
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
|
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +386,7 @@ public:
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
auto it = wallet->mapWallet.find(tx->GetHash());
|
auto it = wallet->mapWallet.find(tx->GetHash());
|
||||||
BOOST_CHECK(it != wallet->mapWallet.end());
|
BOOST_CHECK(it != wallet->mapWallet.end());
|
||||||
it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
|
it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1);
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1110,22 +1110,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||||
bool fUpdated = false;
|
bool fUpdated = false;
|
||||||
if (!fInsertedNew)
|
if (!fInsertedNew)
|
||||||
{
|
{
|
||||||
// Merge
|
if (wtxIn.m_confirm.status != wtx.m_confirm.status) {
|
||||||
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock)
|
wtx.m_confirm.status = wtxIn.m_confirm.status;
|
||||||
{
|
wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex;
|
||||||
wtx.hashBlock = wtxIn.hashBlock;
|
wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock;
|
||||||
fUpdated = true;
|
|
||||||
}
|
|
||||||
// If no longer abandoned, update
|
|
||||||
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned())
|
|
||||||
{
|
|
||||||
wtx.hashBlock = wtxIn.hashBlock;
|
|
||||||
fUpdated = true;
|
|
||||||
}
|
|
||||||
if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex))
|
|
||||||
{
|
|
||||||
wtx.nIndex = wtxIn.nIndex;
|
|
||||||
fUpdated = true;
|
fUpdated = true;
|
||||||
|
} else {
|
||||||
|
assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
|
||||||
|
assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
|
||||||
}
|
}
|
||||||
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
|
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
|
||||||
{
|
{
|
||||||
|
@ -1172,8 +1164,19 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::LoadToWallet(const CWalletTx& wtxIn)
|
void CWallet::LoadToWallet(CWalletTx& wtxIn)
|
||||||
{
|
{
|
||||||
|
// If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken.
|
||||||
|
auto locked_chain = LockChain();
|
||||||
|
// If tx hasn't been reorged out of chain while wallet being shutdown
|
||||||
|
// change tx status to UNCONFIRMED and reset hashBlock/nIndex.
|
||||||
|
if (!wtxIn.m_confirm.hashBlock.IsNull()) {
|
||||||
|
if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) {
|
||||||
|
wtxIn.setUnconfirmed();
|
||||||
|
wtxIn.m_confirm.hashBlock = uint256();
|
||||||
|
wtxIn.m_confirm.nIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
uint256 hash = wtxIn.GetHash();
|
uint256 hash = wtxIn.GetHash();
|
||||||
const auto& ins = mapWallet.emplace(hash, wtxIn);
|
const auto& ins = mapWallet.emplace(hash, wtxIn);
|
||||||
CWalletTx& wtx = ins.first->second;
|
CWalletTx& wtx = ins.first->second;
|
||||||
|
@ -1186,14 +1189,14 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn)
|
||||||
auto it = mapWallet.find(txin.prevout.hash);
|
auto it = mapWallet.find(txin.prevout.hash);
|
||||||
if (it != mapWallet.end()) {
|
if (it != mapWallet.end()) {
|
||||||
CWalletTx& prevtx = it->second;
|
CWalletTx& prevtx = it->second;
|
||||||
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
|
if (prevtx.isConflicted()) {
|
||||||
MarkConflicted(prevtx.hashBlock, wtx.GetHash());
|
MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate)
|
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate)
|
||||||
{
|
{
|
||||||
const CTransaction& tx = *ptx;
|
const CTransaction& tx = *ptx;
|
||||||
{
|
{
|
||||||
|
@ -1240,9 +1243,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256
|
||||||
|
|
||||||
CWalletTx wtx(this, ptx);
|
CWalletTx wtx(this, ptx);
|
||||||
|
|
||||||
// Get merkle branch if transaction was found in a block
|
// Block disconnection override an abandoned tx as unconfirmed
|
||||||
if (!block_hash.IsNull())
|
// which means user may have to call abandontransaction again
|
||||||
wtx.SetMerkleBranch(block_hash, posInBlock);
|
wtx.SetConf(status, block_hash, posInBlock);
|
||||||
|
|
||||||
return AddToWallet(wtx, false);
|
return AddToWallet(wtx, false);
|
||||||
}
|
}
|
||||||
|
@ -1302,7 +1305,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
|
||||||
if (currentconfirm == 0 && !wtx.isAbandoned()) {
|
if (currentconfirm == 0 && !wtx.isAbandoned()) {
|
||||||
// If the orig tx was not in block/mempool, none of its spends can be in mempool
|
// If the orig tx was not in block/mempool, none of its spends can be in mempool
|
||||||
assert(!wtx.InMempool());
|
assert(!wtx.InMempool());
|
||||||
wtx.nIndex = -1;
|
wtx.m_confirm.nIndex = 0;
|
||||||
wtx.setAbandoned();
|
wtx.setAbandoned();
|
||||||
wtx.MarkDirty();
|
wtx.MarkDirty();
|
||||||
batch.WriteTx(wtx);
|
batch.WriteTx(wtx);
|
||||||
|
@ -1356,8 +1359,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
|
||||||
if (conflictconfirms < currentconfirm) {
|
if (conflictconfirms < currentconfirm) {
|
||||||
// Block is 'more conflicted' than current confirm; update.
|
// Block is 'more conflicted' than current confirm; update.
|
||||||
// Mark transaction as conflicted with this block.
|
// Mark transaction as conflicted with this block.
|
||||||
wtx.nIndex = -1;
|
wtx.m_confirm.nIndex = 0;
|
||||||
wtx.hashBlock = hashBlock;
|
wtx.m_confirm.hashBlock = hashBlock;
|
||||||
|
wtx.setConflicted();
|
||||||
wtx.MarkDirty();
|
wtx.MarkDirty();
|
||||||
batch.WriteTx(wtx);
|
batch.WriteTx(wtx);
|
||||||
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
|
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
|
||||||
|
@ -1375,8 +1379,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) {
|
void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx)
|
||||||
if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx))
|
{
|
||||||
|
if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx))
|
||||||
return; // Not one of ours
|
return; // Not one of ours
|
||||||
|
|
||||||
// If a transaction changes 'conflicted' state, that changes the balance
|
// If a transaction changes 'conflicted' state, that changes the balance
|
||||||
|
@ -1388,7 +1393,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_h
|
||||||
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
|
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
|
||||||
auto locked_chain = chain().lock();
|
auto locked_chain = chain().lock();
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
|
||||||
|
|
||||||
auto it = mapWallet.find(ptx->GetHash());
|
auto it = mapWallet.find(ptx->GetHash());
|
||||||
if (it != mapWallet.end()) {
|
if (it != mapWallet.end()) {
|
||||||
|
@ -1408,22 +1413,14 @@ void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransaction
|
||||||
const uint256& block_hash = block.GetHash();
|
const uint256& block_hash = block.GetHash();
|
||||||
auto locked_chain = chain().lock();
|
auto locked_chain = chain().lock();
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
// TODO: Temporarily ensure that mempool removals are notified before
|
|
||||||
// connected transactions. This shouldn't matter, but the abandoned
|
|
||||||
// state of transactions in our wallet is currently cleared when we
|
|
||||||
// receive another notification and there is a race condition where
|
|
||||||
// notification of a connected conflict might cause an outside process
|
|
||||||
// to abandon a transaction and then have it inadvertently cleared by
|
|
||||||
// the notification that the conflicted transaction was evicted.
|
|
||||||
|
|
||||||
for (const CTransactionRef& ptx : vtxConflicted) {
|
|
||||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
|
||||||
TransactionRemovedFromMempool(ptx);
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < block.vtx.size(); i++) {
|
for (size_t i = 0; i < block.vtx.size(); i++) {
|
||||||
SyncTransaction(block.vtx[i], block_hash, i);
|
SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i);
|
||||||
TransactionRemovedFromMempool(block.vtx[i]);
|
TransactionRemovedFromMempool(block.vtx[i]);
|
||||||
}
|
}
|
||||||
|
for (const CTransactionRef& ptx : vtxConflicted) {
|
||||||
|
TransactionRemovedFromMempool(ptx);
|
||||||
|
}
|
||||||
|
|
||||||
m_last_block_processed = block_hash;
|
m_last_block_processed = block_hash;
|
||||||
}
|
}
|
||||||
|
@ -1432,8 +1429,12 @@ void CWallet::BlockDisconnected(const CBlock& block) {
|
||||||
auto locked_chain = chain().lock();
|
auto locked_chain = chain().lock();
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
|
// At block disconnection, this will change an abandoned transaction to
|
||||||
|
// be unconfirmed, whether or not the transaction is added back to the mempool.
|
||||||
|
// User may have to call abandontransaction again. It may be addressed in the
|
||||||
|
// future with a stickier abandoned state or even removing abandontransaction call.
|
||||||
for (const CTransactionRef& ptx : block.vtx) {
|
for (const CTransactionRef& ptx : block.vtx) {
|
||||||
SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
|
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2070,7 +2071,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
|
||||||
SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate);
|
SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate);
|
||||||
}
|
}
|
||||||
// scan succeeded, record block as most recent successfully scanned
|
// scan succeeded, record block as most recent successfully scanned
|
||||||
result.last_scanned_block = block_hash;
|
result.last_scanned_block = block_hash;
|
||||||
|
@ -3332,6 +3333,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
|
||||||
|
|
||||||
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
||||||
{
|
{
|
||||||
|
// Even if we don't use this lock in this function, we want to preserve
|
||||||
|
// lock order in LoadToWallet if query of chain state is needed to know
|
||||||
|
// tx status. If lock can't be taken (e.g wallet-tool), tx confirmation
|
||||||
|
// status may be not reliable.
|
||||||
|
auto locked_chain = LockChain();
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
fFirstRunRet = false;
|
fFirstRunRet = false;
|
||||||
|
@ -4042,7 +4048,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
|
||||||
for (const auto& entry : mapWallet) {
|
for (const auto& entry : mapWallet) {
|
||||||
// iterate over all wallet transactions...
|
// iterate over all wallet transactions...
|
||||||
const CWalletTx &wtx = entry.second;
|
const CWalletTx &wtx = entry.second;
|
||||||
if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) {
|
if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) {
|
||||||
// ... which are already in a block
|
// ... which are already in a block
|
||||||
for (const CTxOut &txout : wtx.tx->vout) {
|
for (const CTxOut &txout : wtx.tx->vout) {
|
||||||
// iterate over all their outputs
|
// iterate over all their outputs
|
||||||
|
@ -4085,9 +4091,9 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
|
||||||
unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
|
unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
|
||||||
{
|
{
|
||||||
unsigned int nTimeSmart = wtx.nTimeReceived;
|
unsigned int nTimeSmart = wtx.nTimeReceived;
|
||||||
if (!wtx.hashUnset()) {
|
if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
|
||||||
int64_t blocktime;
|
int64_t blocktime;
|
||||||
if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) {
|
if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) {
|
||||||
int64_t latestNow = wtx.nTimeReceived;
|
int64_t latestNow = wtx.nTimeReceived;
|
||||||
int64_t latestEntry = 0;
|
int64_t latestEntry = 0;
|
||||||
|
|
||||||
|
@ -4115,7 +4121,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
|
||||||
|
|
||||||
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
|
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
|
||||||
} else {
|
} else {
|
||||||
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
|
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nTimeSmart;
|
return nTimeSmart;
|
||||||
|
@ -4233,6 +4239,11 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
|
||||||
// Recover readable keypairs:
|
// Recover readable keypairs:
|
||||||
CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy());
|
CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
std::string backup_filename;
|
std::string backup_filename;
|
||||||
|
// Even if we don't use this lock in this function, we want to preserve
|
||||||
|
// lock order in LoadToWallet if query of chain state is needed to know
|
||||||
|
// tx status. If lock can't be taken, tx confirmation status may be not
|
||||||
|
// reliable.
|
||||||
|
auto locked_chain = dummyWallet.LockChain();
|
||||||
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
|
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -4627,21 +4638,23 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
|
||||||
m_pre_split = false;
|
m_pre_split = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWalletTx::SetMerkleBranch(const uint256& block_hash, int posInBlock)
|
void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock)
|
||||||
{
|
{
|
||||||
|
// Update tx status
|
||||||
|
m_confirm.status = status;
|
||||||
|
|
||||||
// Update the tx's hashBlock
|
// Update the tx's hashBlock
|
||||||
hashBlock = block_hash;
|
m_confirm.hashBlock = block_hash;
|
||||||
|
|
||||||
// set the position of the transaction in the block
|
// set the position of the transaction in the block
|
||||||
nIndex = posInBlock;
|
m_confirm.nIndex = posInBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
|
int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
|
||||||
{
|
{
|
||||||
if (hashUnset())
|
if (isUnconfirmed() || isAbandoned()) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1);
|
return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const
|
int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const
|
||||||
|
|
|
@ -396,7 +396,9 @@ class CWalletTx
|
||||||
private:
|
private:
|
||||||
const CWallet* pwallet;
|
const CWallet* pwallet;
|
||||||
|
|
||||||
/** Constant used in hashBlock to indicate tx has been abandoned */
|
/** Constant used in hashBlock to indicate tx has been abandoned, only used at
|
||||||
|
* serialization/deserialization to avoid ambiguity with conflicted.
|
||||||
|
*/
|
||||||
static const uint256 ABANDON_HASH;
|
static const uint256 ABANDON_HASH;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -457,9 +459,7 @@ public:
|
||||||
mutable CAmount nChangeCached;
|
mutable CAmount nChangeCached;
|
||||||
|
|
||||||
CWalletTx(const CWallet* pwalletIn, CTransactionRef arg)
|
CWalletTx(const CWallet* pwalletIn, CTransactionRef arg)
|
||||||
: tx(std::move(arg)),
|
: tx(std::move(arg))
|
||||||
hashBlock(uint256()),
|
|
||||||
nIndex(-1)
|
|
||||||
{
|
{
|
||||||
Init(pwalletIn);
|
Init(pwalletIn);
|
||||||
}
|
}
|
||||||
|
@ -477,16 +477,37 @@ public:
|
||||||
fInMempool = false;
|
fInMempool = false;
|
||||||
nChangeCached = 0;
|
nChangeCached = 0;
|
||||||
nOrderPos = -1;
|
nOrderPos = -1;
|
||||||
|
m_confirm = Confirmation{};
|
||||||
}
|
}
|
||||||
|
|
||||||
CTransactionRef tx;
|
CTransactionRef tx;
|
||||||
uint256 hashBlock;
|
|
||||||
/* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
|
/* New transactions start as UNCONFIRMED. At BlockConnected,
|
||||||
* block in the chain we know this or any in-wallet dependency conflicts
|
* they will transition to CONFIRMED. In case of reorg, at BlockDisconnected,
|
||||||
* with. Older clients interpret nIndex == -1 as unconfirmed for backward
|
* they roll back to UNCONFIRMED. If we detect a conflicting transaction at
|
||||||
* compatibility.
|
* block connection, we update conflicted tx and its dependencies as CONFLICTED.
|
||||||
|
* If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED
|
||||||
|
* by using the abandontransaction call. This last status may be override by a CONFLICTED
|
||||||
|
* or CONFIRMED transition.
|
||||||
*/
|
*/
|
||||||
int nIndex;
|
enum Status {
|
||||||
|
UNCONFIRMED,
|
||||||
|
CONFIRMED,
|
||||||
|
CONFLICTED,
|
||||||
|
ABANDONED
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed.
|
||||||
|
* This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state
|
||||||
|
* where they instead point to block hash and index of the deepest conflicting tx.
|
||||||
|
*/
|
||||||
|
struct Confirmation {
|
||||||
|
Status status = UNCONFIRMED;
|
||||||
|
uint256 hashBlock = uint256();
|
||||||
|
int nIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Confirmation m_confirm;
|
||||||
|
|
||||||
template<typename Stream>
|
template<typename Stream>
|
||||||
void Serialize(Stream& s) const
|
void Serialize(Stream& s) const
|
||||||
|
@ -502,7 +523,9 @@ public:
|
||||||
std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch
|
std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch
|
||||||
std::vector<char> dummy_vector2; //!< Used to be vtxPrev
|
std::vector<char> dummy_vector2; //!< Used to be vtxPrev
|
||||||
bool dummy_bool = false; //!< Used to be fSpent
|
bool dummy_bool = false; //!< Used to be fSpent
|
||||||
s << tx << hashBlock << dummy_vector1 << nIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
|
uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock;
|
||||||
|
int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex;
|
||||||
|
s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Stream>
|
template<typename Stream>
|
||||||
|
@ -513,7 +536,25 @@ public:
|
||||||
std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch
|
std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch
|
||||||
std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev
|
std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev
|
||||||
bool dummy_bool; //! Used to be fSpent
|
bool dummy_bool; //! Used to be fSpent
|
||||||
s >> tx >> hashBlock >> dummy_vector1 >> nIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
|
int serializedIndex;
|
||||||
|
s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
|
||||||
|
|
||||||
|
/* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to
|
||||||
|
* the earliest block in the chain we know this or any in-wallet ancestor conflicts
|
||||||
|
* with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned.
|
||||||
|
* In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or
|
||||||
|
* unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward
|
||||||
|
* compatibility (pre-commit 9ac63d6).
|
||||||
|
*/
|
||||||
|
if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
|
||||||
|
m_confirm.hashBlock = uint256();
|
||||||
|
setAbandoned();
|
||||||
|
} else if (serializedIndex == -1) {
|
||||||
|
setConflicted();
|
||||||
|
} else if (!m_confirm.hashBlock.IsNull()) {
|
||||||
|
m_confirm.nIndex = serializedIndex;
|
||||||
|
setConfirmed();
|
||||||
|
}
|
||||||
|
|
||||||
ReadOrderPos(nOrderPos, mapValue);
|
ReadOrderPos(nOrderPos, mapValue);
|
||||||
nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
|
nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
|
||||||
|
@ -590,7 +631,7 @@ public:
|
||||||
// in place.
|
// in place.
|
||||||
std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
|
std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
|
||||||
|
|
||||||
void SetMerkleBranch(const uint256& block_hash, int posInBlock);
|
void SetConf(Status status, const uint256& block_hash, int posInBlock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return depth of transaction in blockchain:
|
* Return depth of transaction in blockchain:
|
||||||
|
@ -607,10 +648,18 @@ public:
|
||||||
* >0 : is a coinbase transaction which matures in this many blocks
|
* >0 : is a coinbase transaction which matures in this many blocks
|
||||||
*/
|
*/
|
||||||
int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const;
|
int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const;
|
||||||
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
|
bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
|
||||||
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
|
void setAbandoned()
|
||||||
void setAbandoned() { hashBlock = ABANDON_HASH; }
|
{
|
||||||
|
m_confirm.status = CWalletTx::ABANDONED;
|
||||||
|
m_confirm.hashBlock = uint256();
|
||||||
|
m_confirm.nIndex = 0;
|
||||||
|
}
|
||||||
|
bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; }
|
||||||
|
void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
|
||||||
|
bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; }
|
||||||
|
void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
|
||||||
|
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
|
||||||
const uint256& GetHash() const { return tx->GetHash(); }
|
const uint256& GetHash() const { return tx->GetHash(); }
|
||||||
bool IsCoinBase() const { return tx->IsCoinBase(); }
|
bool IsCoinBase() const { return tx->IsCoinBase(); }
|
||||||
bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const;
|
bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const;
|
||||||
|
@ -750,7 +799,7 @@ private:
|
||||||
* Abandoned state should probably be more carefully tracked via different
|
* Abandoned state should probably be more carefully tracked via different
|
||||||
* posInBlock signals or by checking mempool presence when necessary.
|
* posInBlock signals or by checking mempool presence when necessary.
|
||||||
*/
|
*/
|
||||||
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
|
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
|
||||||
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
|
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
|
||||||
|
@ -762,7 +811,7 @@ private:
|
||||||
|
|
||||||
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
|
/* 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. */
|
* 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, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/* the HD chain data model (external chain counters) */
|
/* the HD chain data model (external chain counters) */
|
||||||
CHDChain hdChain;
|
CHDChain hdChain;
|
||||||
|
@ -897,6 +946,9 @@ public:
|
||||||
bool IsLocked() const;
|
bool IsLocked() const;
|
||||||
bool Lock();
|
bool Lock();
|
||||||
|
|
||||||
|
/** Interface to assert chain access and if successful lock it */
|
||||||
|
std::unique_ptr<interfaces::Chain::Lock> LockChain() { return m_chain ? m_chain->lock() : nullptr; }
|
||||||
|
|
||||||
std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
|
std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
|
||||||
|
|
||||||
typedef std::multimap<int64_t, CWalletTx*> TxItems;
|
typedef std::multimap<int64_t, CWalletTx*> TxItems;
|
||||||
|
@ -1042,7 +1094,7 @@ public:
|
||||||
|
|
||||||
void MarkDirty();
|
void MarkDirty();
|
||||||
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
|
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
|
||||||
void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void TransactionAddedToMempool(const CTransactionRef& tx) override;
|
void TransactionAddedToMempool(const CTransactionRef& tx) override;
|
||||||
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
|
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
|
||||||
void BlockDisconnected(const CBlock& block) override;
|
void BlockDisconnected(const CBlock& block) override;
|
||||||
|
|
|
@ -131,6 +131,7 @@ BASE_SCRIPTS = [
|
||||||
'wallet_createwallet.py --usecli',
|
'wallet_createwallet.py --usecli',
|
||||||
'wallet_watchonly.py',
|
'wallet_watchonly.py',
|
||||||
'wallet_watchonly.py --usecli',
|
'wallet_watchonly.py --usecli',
|
||||||
|
'wallet_reorgsrestore.py',
|
||||||
'interface_http.py',
|
'interface_http.py',
|
||||||
'interface_rpc.py',
|
'interface_rpc.py',
|
||||||
'rpc_psbt.py',
|
'rpc_psbt.py',
|
||||||
|
|
105
test/functional/wallet_reorgsrestore.py
Executable file
105
test/functional/wallet_reorgsrestore.py
Executable file
|
@ -0,0 +1,105 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2019 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
"""Test tx status in case of reorgs while wallet being shutdown.
|
||||||
|
|
||||||
|
Wallet txn status rely on block connection/disconnection for its
|
||||||
|
accuracy. In case of reorgs happening while wallet being shutdown
|
||||||
|
block updates are not going to be received. At wallet loading, we
|
||||||
|
check against chain if confirmed txn are still in chain and change
|
||||||
|
their status if block in which they have been included has been
|
||||||
|
disconnected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import (
|
||||||
|
assert_equal,
|
||||||
|
connect_nodes,
|
||||||
|
disconnect_nodes,
|
||||||
|
)
|
||||||
|
|
||||||
|
class ReorgsRestoreTest(BitcoinTestFramework):
|
||||||
|
def set_test_params(self):
|
||||||
|
self.num_nodes = 3
|
||||||
|
|
||||||
|
def skip_test_if_missing_module(self):
|
||||||
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
# Send a tx from which to conflict outputs later
|
||||||
|
txid_conflict_from = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
self.sync_blocks()
|
||||||
|
|
||||||
|
# Disconnect node1 from others to reorg its chain later
|
||||||
|
disconnect_nodes(self.nodes[0], 1)
|
||||||
|
disconnect_nodes(self.nodes[1], 2)
|
||||||
|
connect_nodes(self.nodes[0], 2)
|
||||||
|
|
||||||
|
# Send a tx to be unconfirmed later
|
||||||
|
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
|
||||||
|
tx = self.nodes[0].gettransaction(txid)
|
||||||
|
self.nodes[0].generate(4)
|
||||||
|
tx_before_reorg = self.nodes[0].gettransaction(txid)
|
||||||
|
assert_equal(tx_before_reorg["confirmations"], 4)
|
||||||
|
|
||||||
|
# Disconnect node0 from node2 to broadcast a conflict on their respective chains
|
||||||
|
disconnect_nodes(self.nodes[0], 2)
|
||||||
|
nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10"))
|
||||||
|
inputs = []
|
||||||
|
inputs.append({"txid": txid_conflict_from, "vout": nA})
|
||||||
|
outputs_1 = {}
|
||||||
|
outputs_2 = {}
|
||||||
|
|
||||||
|
# Create a conflicted tx broadcast on node0 chain and conflicting tx broadcast on node1 chain. Both spend from txid_conflict_from
|
||||||
|
outputs_1[self.nodes[0].getnewaddress()] = Decimal("9.99998")
|
||||||
|
outputs_2[self.nodes[0].getnewaddress()] = Decimal("9.99998")
|
||||||
|
conflicted = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_1))
|
||||||
|
conflicting = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_2))
|
||||||
|
|
||||||
|
conflicted_txid = self.nodes[0].sendrawtransaction(conflicted["hex"])
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
conflicting_txid = self.nodes[2].sendrawtransaction(conflicting["hex"])
|
||||||
|
self.nodes[2].generate(9)
|
||||||
|
|
||||||
|
# Reconnect node0 and node2 and check that conflicted_txid is effectively conflicted
|
||||||
|
connect_nodes(self.nodes[0], 2)
|
||||||
|
self.sync_blocks([self.nodes[0], self.nodes[2]])
|
||||||
|
conflicted = self.nodes[0].gettransaction(conflicted_txid)
|
||||||
|
conflicting = self.nodes[0].gettransaction(conflicting_txid)
|
||||||
|
assert_equal(conflicted["confirmations"], -9)
|
||||||
|
assert_equal(conflicted["walletconflicts"][0], conflicting["txid"])
|
||||||
|
|
||||||
|
# Node0 wallet is shutdown
|
||||||
|
self.stop_node(0)
|
||||||
|
self.start_node(0)
|
||||||
|
|
||||||
|
# The block chain re-orgs and the tx is included in a different block
|
||||||
|
self.nodes[1].generate(9)
|
||||||
|
self.nodes[1].sendrawtransaction(tx["hex"])
|
||||||
|
self.nodes[1].generate(1)
|
||||||
|
self.nodes[1].sendrawtransaction(conflicted["hex"])
|
||||||
|
self.nodes[1].generate(1)
|
||||||
|
|
||||||
|
# Node0 wallet file is loaded on longest sync'ed node1
|
||||||
|
self.stop_node(1)
|
||||||
|
self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak'))
|
||||||
|
shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, 'regtest', 'wallet.dat'))
|
||||||
|
self.start_node(1)
|
||||||
|
tx_after_reorg = self.nodes[1].gettransaction(txid)
|
||||||
|
# Check that normal confirmed tx is confirmed again but with different blockhash
|
||||||
|
assert_equal(tx_after_reorg["confirmations"], 2)
|
||||||
|
assert(tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"])
|
||||||
|
conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid)
|
||||||
|
# Check that conflicted tx is confirmed again with blockhash different than previously conflicting tx
|
||||||
|
assert_equal(conflicted_after_reorg["confirmations"], 1)
|
||||||
|
assert(conflicting["blockhash"] != conflicted_after_reorg["blockhash"])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ReorgsRestoreTest().main()
|
Loading…
Add table
Reference in a new issue