Skip to content

Commit

Permalink
[Wallet] Do not store Merkle branches in the wallet
Browse files Browse the repository at this point in the history
Backport of bitcoin/bitcoin#6550

Assume that when a wallet transaction has a valid block hash and
transaction position
in it, the transaction is actually there. We're already trusting wallet
data in a
much more fundamental way anyway.

To prevent backward compatibility issues, a new record is used for
storing the
block locator in the wallet. Old wallets will see a wallet file
synchronized up
to the genesis block, and rescan automatically.
  • Loading branch information
Warrows authored and wqking committed May 23, 2020
1 parent f51456b commit d07f290
Show file tree
Hide file tree
Showing 10 changed files with 23 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/chainparams.cpp
Expand Up @@ -195,7 +195,7 @@ class CMainParams : public CChainParams
txNew.vout[0].scriptPubKey = CScript() << ParseHex("04c10e83b2703ccf322f7dbd62dd5855ac7c10bd055814ce121ba32607d573b8810c02c0582aed05b4deb9c4b77b26d92428c61256cd42774babea0a073b2ed0c9") << OP_CHECKSIG;
genesis.vtx.push_back(txNew);
genesis.hashPrevBlock = 0;
genesis.hashMerkleRoot = genesis.BuildMerkleTree();
genesis.hashMerkleRoot = genesis.ComputeMerkleRoot();
genesis.nVersion = 1;
genesis.nTime = 1454124731;
genesis.nBits = 0x1e0ffff0;
Expand Down
9 changes: 7 additions & 2 deletions src/main.cpp
Expand Up @@ -4546,6 +4546,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
LogPrint("debug", "%s: block=%s is proof of stake=%d\n", __func__, block.GetHash().ToString().c_str(), IsPoS);


if (block.fChecked)
return true;

// Check that the header is valid (particularly PoW). This is mostly
// redundant with the call in AcceptBlockHeader.
if (!CheckBlockHeader(block, state, !IsPoS))
Expand All @@ -4564,7 +4567,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
// Check the merkle root.
if (fCheckMerkleRoot) {
bool mutated;
uint256 hashMerkleRoot2 = block.BuildMerkleTree(&mutated);
uint256 hashMerkleRoot2 = block.ComputeMerkleRoot(&mutated);
if (block.hashMerkleRoot != hashMerkleRoot2)
return state.DoS(100, error("%s : hashMerkleRoot mismatch", __func__),
REJECT_INVALID, "bad-txnmrklroot", true);
Expand Down Expand Up @@ -4699,7 +4702,6 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
}
}


unsigned int nSigOps = 0;
for (const CTransaction& tx : block.vtx) {
nSigOps += GetLegacySigOpCount(tx);
Expand All @@ -4709,6 +4711,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo
return state.DoS(100, error("%s : out-of-bounds SigOpCount", __func__),
REJECT_INVALID, "bad-blk-sigops", true);

if (fCheckPOW && fCheckMerkleRoot && fCheckSig)
block.fChecked = true;

return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/miner.cpp
Expand Up @@ -589,7 +589,7 @@ void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int&
assert(txCoinbase.vin[0].scriptSig.size() <= 100);

pblock->vtx[0] = txCoinbase;
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
pblock->hashMerkleRoot = pblock->ComputeMerkleRoot();
}

#ifdef ENABLE_WALLET
Expand Down
39 changes: 2 additions & 37 deletions src/primitives/block.cpp
Expand Up @@ -21,7 +21,7 @@ uint256 CBlockHeader::GetHash() const
return Hash(BEGIN(nVersion), END(nAccumulatorCheckpoint));
}

uint256 CBlock::BuildMerkleTree(bool* fMutated) const
uint256 CBlock::ComputeMerkleRoot(bool* fMutated) const
{
/* WARNING! If you're reading this because you're learning about crypto
and/or designing a new system that will use merkle trees, keep in mind
Expand Down Expand Up @@ -58,7 +58,7 @@ uint256 CBlock::BuildMerkleTree(bool* fMutated) const
known ways of changing the transactions without affecting the merkle
root.
*/
vMerkleTree.clear();
std::vector<uint256> vMerkleTree;
vMerkleTree.reserve(vtx.size() * 2 + 16); // Safe upper bound for the number of total nodes.
for (std::vector<CTransaction>::const_iterator it(vtx.begin()); it != vtx.end(); ++it)
vMerkleTree.push_back(it->GetHash());
Expand All @@ -84,37 +84,6 @@ uint256 CBlock::BuildMerkleTree(bool* fMutated) const
return (vMerkleTree.empty() ? uint256() : vMerkleTree.back());
}

std::vector<uint256> CBlock::GetMerkleBranch(int nIndex) const
{
if (vMerkleTree.empty())
BuildMerkleTree();
std::vector<uint256> vMerkleBranch;
int j = 0;
for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2)
{
int i = std::min(nIndex^1, nSize-1);
vMerkleBranch.push_back(vMerkleTree[j+i]);
nIndex >>= 1;
j += nSize;
}
return vMerkleBranch;
}

uint256 CBlock::CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex)
{
if (nIndex == -1)
return uint256();
for (std::vector<uint256>::const_iterator it(vMerkleBranch.begin()); it != vMerkleBranch.end(); ++it)
{
if (nIndex & 1)
hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash));
else
hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it));
nIndex >>= 1;
}
return hash;
}

std::string CBlock::ToString() const
{
std::stringstream s;
Expand All @@ -129,10 +98,6 @@ std::string CBlock::ToString() const
{
s << " " << vtx[i].ToString() << "\n";
}
s << " vMerkleTree: ";
for (unsigned int i = 0; i < vMerkleTree.size(); i++)
s << " " << vMerkleTree[i].ToString();
s << "\n";
return s.str();
}

Expand Down
10 changes: 4 additions & 6 deletions src/primitives/block.h
Expand Up @@ -94,7 +94,7 @@ class CBlock : public CBlockHeader

// memory only
mutable CScript payee;
mutable std::vector<uint256> vMerkleTree;
mutable bool fChecked;

CBlock()
{
Expand All @@ -121,7 +121,7 @@ class CBlock : public CBlockHeader
{
CBlockHeader::SetNull();
vtx.clear();
vMerkleTree.clear();
fChecked = false;
payee = CScript();
vchBlockSig.clear();
}
Expand Down Expand Up @@ -157,14 +157,12 @@ class CBlock : public CBlockHeader
return IsProofOfStake()? std::make_pair(vtx[1].vin[0].prevout, nTime) : std::make_pair(COutPoint(), (unsigned int)0);
}

// Build the in-memory merkle tree for this block and return the merkle root.
// Build the merkle tree for this block and return the merkle root.
// If non-NULL, *mutated is set to whether mutation was detected in the merkle
// tree (a duplication of transactions in the block leading to an identical
// merkle root).
uint256 BuildMerkleTree(bool* mutated = NULL) const;
uint256 ComputeMerkleRoot(bool* mutated = NULL) const;

std::vector<uint256> GetMerkleBranch(int nIndex) const;
static uint256 CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex);
std::string ToString() const;
void print() const;
};
Expand Down
2 changes: 1 addition & 1 deletion src/test/miner_tests.cpp
Expand Up @@ -81,7 +81,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
pblock->vtx[0] = CTransaction(txCoinbase);
if (txFirst.size() < 2)
txFirst.push_back(new CTransaction(pblock->vtx[0]));
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
pblock->hashMerkleRoot = pblock->ComputeMerkleRoot();
pblock->nNonce = blockinfo[i].nonce;
CValidationState state;
BOOST_CHECK(ProcessNewBlock(state, NULL, pblock));
Expand Down
2 changes: 1 addition & 1 deletion src/test/pmt_tests.cpp
Expand Up @@ -45,7 +45,7 @@ BOOST_AUTO_TEST_CASE(pmt_test1)
}

// calculate actual merkle root and height
uint256 merkleRoot1 = block.BuildMerkleTree();
uint256 merkleRoot1 = block.ComputeMerkleRoot();
std::vector<uint256> vTxid(nTx, 0);
for (unsigned int j=0; j<nTx; j++)
vTxid[j] = block.vtx[j].GetHash();
Expand Down
14 changes: 1 addition & 13 deletions src/wallet.cpp
Expand Up @@ -703,8 +703,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet)
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex)) {
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
if (wtxIn.nIndex != -1 && wtxIn.nIndex != wtx.nIndex) {
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
Expand Down Expand Up @@ -3744,15 +3743,11 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
if (block.vtx[nIndex] == *(CTransaction*)this)
break;
if (nIndex == (int)block.vtx.size()) {
vMerkleBranch.clear();
nIndex = -1;
LogPrintf("ERROR: SetMerkleBranch() : couldn't find tx in block\n");
return 0;
}

// Fill in merkle branch
vMerkleBranch = block.GetMerkleBranch(nIndex);

// Is the tx in a block that's in the main chain
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi == mapBlockIndex.end())
Expand All @@ -3778,13 +3773,6 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex*& pindexRet) const
if (!pindex || !chainActive.Contains(pindex))
return 0;

// Make sure the merkle branch connects to this block
if (!fMerkleVerified) {
if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot)
return 0;
fMerkleVerified = true;
}

pindexRet = pindex;
return chainActive.Height() - pindex->nHeight + 1;
}
Expand Down
7 changes: 1 addition & 6 deletions src/wallet.h
Expand Up @@ -771,13 +771,8 @@ class CMerkleTx : public CTransaction

public:
uint256 hashBlock;
std::vector<uint256> vMerkleBranch;
int nIndex;

// memory only
mutable bool fMerkleVerified;


CMerkleTx()
{
Init();
Expand All @@ -792,14 +787,14 @@ class CMerkleTx : public CTransaction
{
hashBlock = 0;
nIndex = -1;
fMerkleVerified = false;
}

ADD_SERIALIZE_METHODS;

template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{
std::vector<uint256> vMerkleBranch; // For compatibility with older versions.
READWRITE(*(CTransaction*)this);
nVersion = this->nVersion;
READWRITE(hashBlock);
Expand Down
6 changes: 4 additions & 2 deletions src/walletdb.cpp
Expand Up @@ -198,12 +198,14 @@ bool CWalletDB::EraseMultiSig(const CScript& dest)
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
{
nWalletDBUpdated++;
return Write(std::string("bestblock"), locator);
Write(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
return Write(std::string("bestblock_nomerkle"), locator);
}

bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
{
return Read(std::string("bestblock"), locator);
if (Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true;
return Read(std::string("bestblock_nomerkle"), locator);
}

bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext)
Expand Down

0 comments on commit d07f290

Please sign in to comment.