Do not store Merkle branches in the wallet.

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.

(cherry picked from commit bitcoin/bitcoin@391dff16fe)
This commit is contained in:
Pieter Wuille 2015-08-11 21:03:31 +02:00 committed by Jack Grigg
parent ee3055f596
commit 204d0c37ab
17 changed files with 42 additions and 92 deletions

View File

@ -4,3 +4,11 @@ release-notes at release time)
Notable changes
===============
Merkle branches removed from wallet
-----------------------------------
Previously, every wallet transaction stored a Merkle branch to prove its
presence in blocks. This wasn't being used for more than an expensive
sanity check. Since 5.1.0, these are no longer stored. When loading a
5.1.0 wallet into an older version, it will automatically rescan to avoid
failed checks.

View File

@ -43,7 +43,7 @@ static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesi
genesis.nVersion = nVersion;
genesis.vtx.push_back(txNew);
genesis.hashPrevBlock.SetNull();
genesis.hashMerkleRoot = genesis.BuildMerkleTree();
genesis.hashMerkleRoot = genesis.ComputeMerkleRoot();
return genesis;
}

View File

@ -57,7 +57,7 @@ static inline size_t RecursiveDynamicUsage(const CMutableTransaction& tx) {
}
static inline size_t RecursiveDynamicUsage(const CBlock& block) {
size_t mem = memusage::DynamicUsage(block.vtx) + memusage::DynamicUsage(block.vMerkleTree);
size_t mem = memusage::DynamicUsage(block.vtx);
for (std::vector<CTransaction>::const_iterator it = block.vtx.begin(); it != block.vtx.end(); it++) {
mem += RecursiveDynamicUsage(*it);
}

View File

@ -154,7 +154,7 @@ TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
// Create a fake block. It doesn't need to contain any transactions; we just
// need it to be in the global state when our fake view is used.
CBlock block;
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -226,14 +226,14 @@ TEST(Validation, ReceivedBlockTransactions) {
// Create a fake genesis block
CBlock block1;
block1.vtx.push_back(GetValidSproutReceive(sk, 5, true));
block1.hashMerkleRoot = block1.BuildMerkleTree();
block1.hashMerkleRoot = block1.ComputeMerkleRoot();
CBlockIndex fakeIndex1 {block1};
// Create a fake child block
CBlock block2;
block2.hashPrevBlock = block1.GetHash();
block2.vtx.push_back(GetValidSproutReceive(sk, 10, true));
block2.hashMerkleRoot = block2.BuildMerkleTree();
block2.hashMerkleRoot = block2.ComputeMerkleRoot();
CBlockIndex fakeIndex2 {block2};
fakeIndex2.pprev = &fakeIndex1;

View File

@ -4738,7 +4738,7 @@ bool CheckBlock(const CBlock& block,
// 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("CheckBlock(): hashMerkleRoot mismatch"),
REJECT_INVALID, "bad-txnmrklroot", true);

View File

@ -867,7 +867,7 @@ void IncrementExtraNonce(
assert(txCoinbase.vin[0].scriptSig.size() <= 100);
pblock->vtx[0] = txCoinbase;
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
pblock->hashMerkleRoot = pblock->ComputeMerkleRoot();
if (consensusParams.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_NU5)) {
pblocktemplate->hashAuthDataRoot = pblock->BuildAuthDataMerkleTree();
pblock->hashBlockCommitments = DeriveBlockCommitmentsHash(

View File

@ -37,7 +37,7 @@ uint256 CBlockHeader::GetHash() const
return SerializeHash(*this);
}
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
@ -74,7 +74,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());
@ -100,37 +100,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;
}
// Return 0 if x == 0, otherwise the smallest power of 2 greater than or equal to x.
// Algorithm based on <https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2>.
static uint64_t next_pow2(uint64_t x)
@ -193,9 +162,5 @@ 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();
}

View File

@ -89,9 +89,6 @@ public:
// network and disk
std::vector<CTransaction> vtx;
// memory only
mutable std::vector<uint256> vMerkleTree;
CBlock()
{
SetNull();
@ -115,7 +112,6 @@ public:
{
CBlockHeader::SetNull();
vtx.clear();
vMerkleTree.clear();
}
CBlockHeader GetBlockHeader() const
@ -132,14 +128,11 @@ public:
return block;
}
// 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;
std::vector<uint256> GetMerkleBranch(int nIndex) const;
static uint256 CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMerkleBranch, int nIndex);
uint256 ComputeMerkleRoot(bool* mutated = NULL) const;
// Build the authorizing data Merkle tree for this block and return its
// root.

View File

@ -789,7 +789,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
// block template returned by this RPC is used unmodified. Otherwise,
// these values must be recomputed.
UniValue defaults(UniValue::VOBJ);
defaults.pushKV("merkleroot", pblock->BuildMerkleTree().GetHex());
defaults.pushKV("merkleroot", pblock->ComputeMerkleRoot().GetHex());
defaults.pushKV("chainhistoryroot", pblocktemplate->hashChainHistoryRoot.GetHex());
if (consensus.NetworkUpgradeActive(pindexPrev->nHeight+1, Consensus::UPGRADE_NU5)) {
defaults.pushKV("authdataroot", pblocktemplate->hashAuthDataRoot.GetHex());

View File

@ -189,7 +189,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 = uint256S(blockinfo[i].nonce_hex);
pblock->nSolution = ParseHex(blockinfo[i].solution_hex);

View File

@ -49,7 +49,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, uint256());
for (unsigned int j=0; j<nTx; j++)
vTxid[j] = block.vtx[j].GetHash();

View File

@ -271,7 +271,7 @@ TEST(WalletRPCTests, RPCZsendmanyTaddrToSapling)
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));

View File

@ -233,7 +233,7 @@ TEST(WalletTests, FindUnspentSproutNotes) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -273,7 +273,7 @@ TEST(WalletTests, FindUnspentSproutNotes) {
EXPECT_EQ(0, chainActive.Height());
CBlock block2;
block2.vtx.push_back(wtx2);
block2.hashMerkleRoot = block2.BuildMerkleTree();
block2.hashMerkleRoot = block2.ComputeMerkleRoot();
block2.hashPrevBlock = blockHash;
auto blockHash2 = block2.GetHash();
CBlockIndex fakeIndex2 {block2};
@ -336,7 +336,7 @@ TEST(WalletTests, FindUnspentSproutNotes) {
EXPECT_EQ(1, chainActive.Height());
CBlock block3;
block3.vtx.push_back(wtx3);
block3.hashMerkleRoot = block3.BuildMerkleTree();
block3.hashMerkleRoot = block3.ComputeMerkleRoot();
block3.hashPrevBlock = blockHash2;
auto blockHash3 = block3.GetHash();
CBlockIndex fakeIndex3 {block3};
@ -723,7 +723,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -852,7 +852,7 @@ TEST(WalletTests, GetConflictedOrchardNotes) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
fakeIndex.hashFinalOrchardRoot = orchardTree.root();
@ -969,7 +969,7 @@ TEST(WalletTests, SproutNullifierIsSpent) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx2);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -1023,7 +1023,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -1112,7 +1112,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -1241,7 +1241,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -1316,7 +1316,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
EXPECT_EQ(0, chainActive.Height());
CBlock block2;
block2.vtx.push_back(wtx2);
block2.hashMerkleRoot = block2.BuildMerkleTree();
block2.hashMerkleRoot = block2.ComputeMerkleRoot();
block2.hashPrevBlock = blockHash;
auto blockHash2 = block2.GetHash();
CBlockIndex fakeIndex2 {block2};
@ -2100,7 +2100,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
@ -2249,7 +2249,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
SproutMerkleTree sproutTree;
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));

View File

@ -1696,7 +1696,7 @@ void TestWTxStatus(const Consensus::Params consensusParams, const int delta) {
BOOST_CHECK_EQUAL(height, chainActive.Height());
CBlock block;
if (has_trx) block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
block.hashMerkleRoot = block.ComputeMerkleRoot();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
fakeIndex.nHeight = height+1;

View File

@ -3169,9 +3169,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, CWalletDB* pwalletdb)
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex))
if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex))
{
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
@ -6658,13 +6657,9 @@ void CMerkleTx::SetMerkleBranch(const CBlock& block)
break;
if (nIndex == (int)block.vtx.size())
{
vMerkleBranch.clear();
nIndex = -1;
LogPrintf("ERROR: SetMerkleBranch(): couldn't find tx in block\n");
}
// Fill in merkle branch
vMerkleBranch = block.GetMerkleBranch(nIndex);
}
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
@ -6681,14 +6676,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;
}

View File

@ -372,13 +372,8 @@ private:
public:
uint256 hashBlock;
std::vector<uint256> vMerkleBranch;
int nIndex;
// memory only
mutable bool fMerkleVerified;
CMerkleTx()
{
Init();
@ -393,13 +388,13 @@ public:
{
hashBlock = uint256();
nIndex = -1;
fMerkleVerified = false;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
std::vector<uint256> vMerkleBranch; // For compatibility with older versions.
READWRITE(*(CTransaction*)this);
READWRITE(hashBlock);
READWRITE(vMerkleBranch);

View File

@ -276,12 +276,14 @@ bool CWalletDB::EraseWatchOnly(const CScript &dest)
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
{
nWalletDBUpdateCounter++;
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)