Auto merge of #3170 - ebfull:sapling-merkle-tree, r=ebfull

Sapling merkle tree implementation

Closes #3056.

Please also review https://github.com/zcash/librustzcash/pull/8

This PR:

1. Introduces ZCSaplingIncrementalMerkleTree using Pedersen hashes.
2. Adds support for Sapling anchors into consensus rules. (Adds commitments, checks anchors are correct, handles block (dis)connects, etc.)
3. Handles mempool eviction for obsolete anchors.
4. Enforces correctness of block's Sapling root field
5. Changes miner to correctly apply the Sapling root to the block header
6. Handles mempool consistency checks for anchors
This commit is contained in:
Homu
2018-05-07 20:37:46 -07:00
37 changed files with 1166 additions and 309 deletions

View File

@@ -1306,13 +1306,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
}
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) {
if (pool.nullifierExists(nf, SPROUT_NULLIFIER)) {
if (pool.nullifierExists(nf, SPROUT)) {
return false;
}
}
}
for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
if (pool.nullifierExists(spendDescription.nullifier, SAPLING_NULLIFIER)) {
if (pool.nullifierExists(spendDescription.nullifier, SAPLING)) {
return false;
}
}
@@ -2150,8 +2150,19 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
}
}
// set the old best anchor back
view.PopAnchor(blockUndo.old_tree_root);
// set the old best Sprout anchor back
view.PopAnchor(blockUndo.old_sprout_tree_root, SPROUT);
// set the old best Sapling anchor back
// We can get this from the `hashFinalSaplingRoot` of the last block
// However, this is only reliable if the last block was on or after
// the Sapling activation height. Otherwise, the last anchor was the
// empty root.
if (NetworkUpgradeActive(pindex->pprev->nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
view.PopAnchor(pindex->pprev->hashFinalSaplingRoot, SAPLING);
} else {
view.PopAnchor(ZCSaplingIncrementalMerkleTree::empty_root(), SAPLING);
}
// move best block pointer to prevout block
view.SetBestBlock(pindex->pprev->GetBlockHash());
@@ -2296,9 +2307,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
view.SetBestBlock(pindex->GetBlockHash());
// Before the genesis block, there was an empty tree
ZCIncrementalMerkleTree tree;
pindex->hashAnchor = tree.root();
pindex->hashSproutAnchor = tree.root();
// The genesis block contained no JoinSplits
pindex->hashAnchorEnd = pindex->hashAnchor;
pindex->hashFinalSproutRoot = pindex->hashSproutAnchor;
}
return true;
}
@@ -2331,22 +2342,25 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// Construct the incremental merkle tree at the current
// block position,
auto old_tree_root = view.GetBestAnchor();
auto old_sprout_tree_root = view.GetBestAnchor(SPROUT);
// saving the top anchor in the block index as we go.
if (!fJustCheck) {
pindex->hashAnchor = old_tree_root;
pindex->hashSproutAnchor = old_sprout_tree_root;
}
ZCIncrementalMerkleTree tree;
ZCIncrementalMerkleTree sprout_tree;
// This should never fail: we should always be able to get the root
// that is on the tip of our chain
assert(view.GetAnchorAt(old_tree_root, tree));
assert(view.GetSproutAnchorAt(old_sprout_tree_root, sprout_tree));
{
// Consistency check: the root of the tree we're given should
// match what we asked for.
assert(tree.root() == old_tree_root);
assert(sprout_tree.root() == old_sprout_tree_root);
}
ZCSaplingIncrementalMerkleTree sapling_tree;
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
// Grab the consensus branch ID for the block's height
auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, Params().GetConsensus());
@@ -2404,19 +2418,34 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
BOOST_FOREACH(const uint256 &note_commitment, joinsplit.commitments) {
// Insert the note commitments into our temporary tree.
tree.append(note_commitment);
sprout_tree.append(note_commitment);
}
}
BOOST_FOREACH(const OutputDescription &outputDescription, tx.vShieldedOutput) {
sapling_tree.append(outputDescription.cm);
}
vPos.push_back(std::make_pair(tx.GetHash(), pos));
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
}
view.PushAnchor(tree);
view.PushSproutAnchor(sprout_tree);
view.PushSaplingAnchor(sapling_tree);
if (!fJustCheck) {
pindex->hashAnchorEnd = tree.root();
pindex->hashFinalSproutRoot = sprout_tree.root();
}
blockundo.old_sprout_tree_root = old_sprout_tree_root;
// If Sapling is active, block.hashFinalSaplingRoot must be the
// same as the root of the Sapling tree
if (NetworkUpgradeActive(pindex->nHeight, chainparams.GetConsensus(), Consensus::UPGRADE_SAPLING)) {
if (block.hashFinalSaplingRoot != sapling_tree.root()) {
return state.DoS(100,
error("ConnectBlock(): block's hashFinalSaplingRoot is incorrect"),
REJECT_INVALID, "bad-sapling-root-in-block");
}
}
blockundo.old_tree_root = old_tree_root;
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
@@ -2659,7 +2688,8 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
if (!ReadBlockFromDisk(block, pindexDelete))
return AbortNode(state, "Failed to read block");
// Apply the block atomically to the chain state.
uint256 anchorBeforeDisconnect = pcoinsTip->GetBestAnchor();
uint256 sproutAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
uint256 saplingAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(pcoinsTip);
@@ -2668,7 +2698,8 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
assert(view.Flush());
}
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
uint256 anchorAfterDisconnect = pcoinsTip->GetBestAnchor();
uint256 sproutAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
uint256 saplingAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
return false;
@@ -2682,10 +2713,15 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
mempool.remove(tx, removed, true);
}
if (anchorBeforeDisconnect != anchorAfterDisconnect) {
if (sproutAnchorBeforeDisconnect != sproutAnchorAfterDisconnect) {
// The anchor may not change between block disconnects,
// in which case we don't want to evict from the mempool yet!
mempool.removeWithAnchor(anchorBeforeDisconnect);
mempool.removeWithAnchor(sproutAnchorBeforeDisconnect, SPROUT);
}
if (saplingAnchorBeforeDisconnect != saplingAnchorAfterDisconnect) {
// The anchor may not change between block disconnects,
// in which case we don't want to evict from the mempool yet!
mempool.removeWithAnchor(saplingAnchorBeforeDisconnect, SAPLING);
}
}
@@ -2693,7 +2729,7 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
UpdateTip(pindexDelete->pprev);
// Get the current commitment tree
ZCIncrementalMerkleTree newTree;
assert(pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), newTree));
assert(pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), newTree));
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
@@ -2727,7 +2763,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
}
// Get the current commitment tree
ZCIncrementalMerkleTree oldTree;
assert(pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), oldTree));
assert(pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), oldTree));
// Apply the block atomically to the chain state.
int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
int64_t nTime3;
@@ -3928,12 +3964,12 @@ bool static LoadBlockIndexDB()
{
CBlockIndex* pindex = item.second;
// - This relationship will always be true even if pprev has multiple
// children, because hashAnchor is technically a property of pprev,
// children, because hashSproutAnchor is technically a property of pprev,
// not its children.
// - This will miss chain tips; we handle the best tip below, and other
// tips will be handled by ConnectTip during a re-org.
if (pindex->pprev) {
pindex->pprev->hashAnchorEnd = pindex->hashAnchor;
pindex->pprev->hashFinalSproutRoot = pindex->hashSproutAnchor;
}
}
@@ -3942,8 +3978,8 @@ bool static LoadBlockIndexDB()
if (it == mapBlockIndex.end())
return true;
chainActive.SetTip(it->second);
// Set hashAnchorEnd for the end of best chain
it->second->hashAnchorEnd = pcoinsTip->GetBestAnchor();
// Set hashFinalSproutRoot for the end of best chain
it->second->hashFinalSproutRoot = pcoinsTip->GetBestAnchor(SPROUT);
PruneBlockIndexCandidates();