318 lines
11 KiB
C++
318 lines
11 KiB
C++
#include <gtest/gtest.h>
|
|
#include <gmock/gmock.h>
|
|
|
|
#include "consensus/merkle.h"
|
|
#include "consensus/upgrades.h"
|
|
#include "consensus/validation.h"
|
|
#include "main.h"
|
|
#include "transaction_builder.h"
|
|
#include "util/test.h"
|
|
|
|
#include <optional>
|
|
|
|
extern bool ReceivedBlockTransactions(
|
|
const CBlock &block,
|
|
CValidationState& state,
|
|
const CChainParams& chainparams,
|
|
CBlockIndex *pindexNew,
|
|
const CDiskBlockPos& pos);
|
|
|
|
void ExpectOptionalAmount(CAmount expected, std::optional<CAmount> actual) {
|
|
EXPECT_TRUE((bool)actual);
|
|
if (actual) {
|
|
EXPECT_EQ(expected, *actual);
|
|
}
|
|
}
|
|
|
|
// Fake a view that optionally contains a single coin.
|
|
class ValidationFakeCoinsViewDB : public CCoinsView {
|
|
public:
|
|
std::optional<std::pair<std::pair<uint256, uint256>, std::pair<CTxOut, int>>> coin;
|
|
|
|
ValidationFakeCoinsViewDB() {}
|
|
ValidationFakeCoinsViewDB(uint256 blockHash, uint256 txid, CTxOut txOut, int nHeight) :
|
|
coin(std::make_pair(std::make_pair(blockHash, txid), std::make_pair(txOut, nHeight))) {}
|
|
~ValidationFakeCoinsViewDB() {}
|
|
|
|
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
|
|
return false;
|
|
}
|
|
|
|
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
|
|
return false;
|
|
}
|
|
|
|
bool GetOrchardAnchorAt(const uint256 &rt, OrchardMerkleFrontier &tree) const {
|
|
return false;
|
|
}
|
|
|
|
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
|
return false;
|
|
}
|
|
|
|
bool GetCoins(const uint256 &txid, CCoins &coins) const {
|
|
if (coin && txid == coin.value().first.second) {
|
|
CCoins newCoins;
|
|
newCoins.vout.resize(2);
|
|
newCoins.vout[0] = coin.value().second.first;
|
|
newCoins.nHeight = coin.value().second.second;
|
|
coins.swap(newCoins);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool HaveCoins(const uint256 &txid) const {
|
|
if (coin && txid == coin.value().first.second) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
uint256 GetBestBlock() const {
|
|
if (coin) {
|
|
return coin.value().first.first;
|
|
} else {
|
|
uint256 a;
|
|
return a;
|
|
}
|
|
}
|
|
|
|
uint256 GetBestAnchor(ShieldedType type) const {
|
|
uint256 a;
|
|
return a;
|
|
}
|
|
|
|
HistoryIndex GetHistoryLength(uint32_t branchId) const {
|
|
return 0;
|
|
}
|
|
|
|
HistoryNode GetHistoryAt(uint32_t branchId, HistoryIndex index) const {
|
|
return HistoryNode();
|
|
}
|
|
|
|
uint256 GetHistoryRoot(uint32_t epochId) const {
|
|
return uint256();
|
|
}
|
|
|
|
bool BatchWrite(CCoinsMap &mapCoins,
|
|
const uint256 &hashBlock,
|
|
const uint256 &hashSproutAnchor,
|
|
const uint256 &hashSaplingAnchor,
|
|
const uint256 &hashOrchardAnchor,
|
|
CAnchorsSproutMap &mapSproutAnchors,
|
|
CAnchorsSaplingMap &mapSaplingAnchors,
|
|
CAnchorsOrchardMap &mapOrchardAnchors,
|
|
CNullifiersMap &mapSproutNullifiers,
|
|
CNullifiersMap &mapSaplingNullifiers,
|
|
CNullifiersMap &mapOrchardNullifiers,
|
|
CHistoryCacheMap &historyCacheMap) {
|
|
return false;
|
|
}
|
|
|
|
bool GetStats(CCoinsStats &stats) const {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class MockCValidationState : public CValidationState {
|
|
public:
|
|
MOCK_METHOD6(DoS, bool(int level, bool ret,
|
|
unsigned int chRejectCodeIn, const std::string strRejectReasonIn,
|
|
bool corruptionIn,
|
|
const std::string &strDebugMessageIn));
|
|
MOCK_METHOD4(Invalid, bool(bool ret,
|
|
unsigned int _chRejectCode, const std::string _strRejectReason,
|
|
const std::string &_strDebugMessage));
|
|
MOCK_METHOD1(Error, bool(std::string strRejectReasonIn));
|
|
MOCK_CONST_METHOD0(IsValid, bool());
|
|
MOCK_CONST_METHOD0(IsInvalid, bool());
|
|
MOCK_CONST_METHOD0(IsError, bool());
|
|
MOCK_CONST_METHOD1(IsInvalid, bool(int &nDoSOut));
|
|
MOCK_CONST_METHOD0(CorruptionPossible, bool());
|
|
MOCK_CONST_METHOD0(GetRejectCode, unsigned int());
|
|
MOCK_CONST_METHOD0(GetRejectReason, std::string());
|
|
MOCK_CONST_METHOD0(GetDebugMessage, std::string());
|
|
};
|
|
|
|
TEST(Validation, ContextualCheckInputsPassesWithCoinbase) {
|
|
// Create fake coinbase transaction
|
|
CMutableTransaction mtx;
|
|
mtx.vin.resize(1);
|
|
CTransaction tx(mtx);
|
|
ASSERT_TRUE(tx.IsCoinBase());
|
|
|
|
// Fake an empty view
|
|
ValidationFakeCoinsViewDB fakeDB;
|
|
CCoinsViewCache view(&fakeDB);
|
|
|
|
for (int idx = Consensus::BASE_SPROUT; idx < Consensus::MAX_NETWORK_UPGRADES; idx++) {
|
|
auto consensusBranchId = NetworkUpgradeInfo[idx].nBranchId;
|
|
CValidationState state;
|
|
// Coinbase transactions have one synthetic input with no prevout.
|
|
PrecomputedTransactionData txdata(tx, {});
|
|
EXPECT_TRUE(ContextualCheckInputs(tx, state, view, false, 0, false, txdata, Params(CBaseChainParams::MAIN).GetConsensus(), consensusBranchId));
|
|
}
|
|
}
|
|
|
|
TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
|
|
SelectParams(CBaseChainParams::REGTEST);
|
|
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, 10);
|
|
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, 20);
|
|
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_BLOSSOM, 30);
|
|
const Consensus::Params& consensusParams = Params(CBaseChainParams::REGTEST).GetConsensus();
|
|
|
|
auto overwinterBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_OVERWINTER].nBranchId;
|
|
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
|
auto blossomBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_BLOSSOM].nBranchId;
|
|
|
|
CBasicKeyStore keystore;
|
|
CKey tsk = AddTestCKeyToKeyStore(keystore);
|
|
auto destination = tsk.GetPubKey().GetID();
|
|
auto scriptPubKey = GetScriptForDestination(destination);
|
|
|
|
// 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 = BlockMerkleRoot(block);
|
|
auto blockHash = block.GetHash();
|
|
CBlockIndex fakeIndex {block};
|
|
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
|
|
chainActive.SetTip(&fakeIndex);
|
|
|
|
// Fake a view containing a single coin.
|
|
CAmount coinValue(5000);
|
|
COutPoint utxo;
|
|
utxo.hash = uint256S("4242424242424242424242424242424242424242424242424242424242424242");
|
|
utxo.n = 0;
|
|
CTxOut txOut;
|
|
txOut.scriptPubKey = scriptPubKey;
|
|
txOut.nValue = coinValue;
|
|
ValidationFakeCoinsViewDB fakeDB(blockHash, utxo.hash, txOut, 12);
|
|
CCoinsViewCache view(&fakeDB);
|
|
|
|
// Create a transparent transaction that spends the coin, targeting
|
|
// a height during the Overwinter epoch.
|
|
auto builder = TransactionBuilder(consensusParams, 15, std::nullopt, &keystore);
|
|
builder.AddTransparentInput(utxo, scriptPubKey, coinValue);
|
|
builder.AddTransparentOutput(destination, 4000);
|
|
auto tx = builder.Build().GetTxOrThrow();
|
|
ASSERT_FALSE(tx.IsCoinBase());
|
|
|
|
// Ensure that the inputs validate against Overwinter.
|
|
CValidationState state;
|
|
PrecomputedTransactionData txdata(tx, {CTxOut(coinValue, scriptPubKey)});
|
|
EXPECT_TRUE(ContextualCheckInputs(
|
|
tx, state, view, true, 0, false, txdata,
|
|
consensusParams, overwinterBranchId));
|
|
|
|
// Attempt to validate the inputs against Sapling. We should be notified
|
|
// that an old consensus branch ID was used for an input.
|
|
MockCValidationState mockState;
|
|
EXPECT_CALL(mockState, DoS(
|
|
10, false, REJECT_INVALID,
|
|
strprintf("old-consensus-branch-id (Expected %s, found %s)",
|
|
HexInt(saplingBranchId),
|
|
HexInt(overwinterBranchId)),
|
|
false, "")).Times(1);
|
|
EXPECT_FALSE(ContextualCheckInputs(
|
|
tx, mockState, view, true, 0, false, txdata,
|
|
consensusParams, saplingBranchId));
|
|
|
|
// Attempt to validate the inputs against Blossom. All we should learn is
|
|
// that the signature is invalid, because we don't check more than one
|
|
// network upgrade back.
|
|
EXPECT_CALL(mockState, DoS(
|
|
100, false, REJECT_INVALID,
|
|
"mandatory-script-verify-flag-failed (Script evaluated without error but finished with a false/empty top stack element)",
|
|
false, "")).Times(1);
|
|
EXPECT_FALSE(ContextualCheckInputs(
|
|
tx, mockState, view, true, 0, false, txdata,
|
|
consensusParams, blossomBranchId));
|
|
|
|
// Tear down
|
|
chainActive.SetTip(NULL);
|
|
mapBlockIndex.erase(blockHash);
|
|
|
|
// Revert to default
|
|
RegtestDeactivateBlossom();
|
|
}
|
|
|
|
TEST(Validation, ReceivedBlockTransactions) {
|
|
SelectParams(CBaseChainParams::REGTEST);
|
|
auto chainParams = Params();
|
|
auto sk = libzcash::SproutSpendingKey::random();
|
|
|
|
// Create a fake genesis block
|
|
CBlock block1;
|
|
block1.vtx.push_back(GetValidSproutReceive(sk, 5, true));
|
|
block1.hashMerkleRoot = BlockMerkleRoot(block1);
|
|
CBlockIndex fakeIndex1 {block1};
|
|
|
|
// Create a fake child block
|
|
CBlock block2;
|
|
block2.hashPrevBlock = block1.GetHash();
|
|
block2.vtx.push_back(GetValidSproutReceive(sk, 10, true));
|
|
block2.hashMerkleRoot = BlockMerkleRoot(block2);
|
|
CBlockIndex fakeIndex2 {block2};
|
|
fakeIndex2.pprev = &fakeIndex1;
|
|
|
|
CDiskBlockPos pos1;
|
|
CDiskBlockPos pos2;
|
|
|
|
// Set initial state of indices
|
|
ASSERT_TRUE(fakeIndex1.RaiseValidity(BLOCK_VALID_TREE));
|
|
ASSERT_TRUE(fakeIndex2.RaiseValidity(BLOCK_VALID_TREE));
|
|
EXPECT_TRUE(fakeIndex1.IsValid(BLOCK_VALID_TREE));
|
|
EXPECT_TRUE(fakeIndex2.IsValid(BLOCK_VALID_TREE));
|
|
EXPECT_FALSE(fakeIndex1.IsValid(BLOCK_VALID_TRANSACTIONS));
|
|
EXPECT_FALSE(fakeIndex2.IsValid(BLOCK_VALID_TRANSACTIONS));
|
|
|
|
// Sprout pool values should not be set
|
|
EXPECT_FALSE((bool)fakeIndex1.nSproutValue);
|
|
EXPECT_FALSE((bool)fakeIndex1.nChainSproutValue);
|
|
EXPECT_FALSE((bool)fakeIndex2.nSproutValue);
|
|
EXPECT_FALSE((bool)fakeIndex2.nChainSproutValue);
|
|
|
|
// Mark the second block's transactions as received first
|
|
CValidationState state;
|
|
EXPECT_TRUE(ReceivedBlockTransactions(block2, state, chainParams, &fakeIndex2, pos2));
|
|
EXPECT_FALSE(fakeIndex1.IsValid(BLOCK_VALID_TRANSACTIONS));
|
|
EXPECT_TRUE(fakeIndex2.IsValid(BLOCK_VALID_TRANSACTIONS));
|
|
|
|
// Sprout pool value delta should now be set for the second block,
|
|
// but not any chain totals
|
|
EXPECT_FALSE((bool)fakeIndex1.nSproutValue);
|
|
EXPECT_FALSE((bool)fakeIndex1.nChainSproutValue);
|
|
{
|
|
SCOPED_TRACE("ExpectOptionalAmount call");
|
|
ExpectOptionalAmount(20, fakeIndex2.nSproutValue);
|
|
}
|
|
EXPECT_FALSE((bool)fakeIndex2.nChainSproutValue);
|
|
|
|
// Now mark the first block's transactions as received
|
|
EXPECT_TRUE(ReceivedBlockTransactions(block1, state, chainParams, &fakeIndex1, pos1));
|
|
EXPECT_TRUE(fakeIndex1.IsValid(BLOCK_VALID_TRANSACTIONS));
|
|
EXPECT_TRUE(fakeIndex2.IsValid(BLOCK_VALID_TRANSACTIONS));
|
|
|
|
// Sprout pool values should now be set for both blocks
|
|
{
|
|
SCOPED_TRACE("ExpectOptionalAmount call");
|
|
ExpectOptionalAmount(10, fakeIndex1.nSproutValue);
|
|
}
|
|
{
|
|
SCOPED_TRACE("ExpectOptionalAmount call");
|
|
ExpectOptionalAmount(10, fakeIndex1.nChainSproutValue);
|
|
}
|
|
{
|
|
SCOPED_TRACE("ExpectOptionalAmount call");
|
|
ExpectOptionalAmount(20, fakeIndex2.nSproutValue);
|
|
}
|
|
{
|
|
SCOPED_TRACE("ExpectOptionalAmount call");
|
|
ExpectOptionalAmount(30, fakeIndex2.nChainSproutValue);
|
|
}
|
|
}
|