Merge pull request #5804 from str4d/consensus-check-coinbase-shielded-spends
Apply HaveShieldedRequirements to coinbase transactions
This commit is contained in:
commit
fbddebc63f
|
@ -545,18 +545,20 @@ TEST(ContextualCheckShieldedInputsTest, BadTxnsInvalidJoinsplitSignature) {
|
||||||
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
||||||
|
|
||||||
MockCValidationState state;
|
MockCValidationState state;
|
||||||
|
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||||
|
CCoinsViewCache view(&baseView);
|
||||||
// during initial block download, for transactions being accepted into the
|
// during initial block download, for transactions being accepted into the
|
||||||
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
||||||
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return true; });
|
||||||
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, 0, false, false, [](const Consensus::Params&) { return false; });
|
||||||
// for transactions that have been mined in a block, DoS ban score should
|
// for transactions that have been mined in a block, DoS ban score should
|
||||||
// always be 100.
|
// always be 100.
|
||||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return true; });
|
||||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, 0, false, true, [](const Consensus::Params&) { return false; });
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
||||||
|
@ -578,9 +580,11 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
||||||
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
||||||
|
|
||||||
MockCValidationState state;
|
MockCValidationState state;
|
||||||
|
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||||
|
CCoinsViewCache view(&baseView);
|
||||||
// Ensure that the transaction validates against Sapling.
|
// Ensure that the transaction validates against Sapling.
|
||||||
EXPECT_TRUE(ContextualCheckShieldedInputs(
|
EXPECT_TRUE(ContextualCheckShieldedInputs(
|
||||||
tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, false,
|
tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, false,
|
||||||
[](const Consensus::Params&) { return false; }));
|
[](const Consensus::Params&) { return false; }));
|
||||||
|
|
||||||
// Attempt to validate the inputs against Blossom. We should be notified
|
// Attempt to validate the inputs against Blossom. We should be notified
|
||||||
|
@ -592,7 +596,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
||||||
HexInt(saplingBranchId)),
|
HexInt(saplingBranchId)),
|
||||||
false, "")).Times(1);
|
false, "")).Times(1);
|
||||||
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
||||||
tx, txdata, state, orchardAuth, consensus, blossomBranchId, false, false,
|
tx, txdata, state, view, orchardAuth, consensus, blossomBranchId, false, false,
|
||||||
[](const Consensus::Params&) { return false; }));
|
[](const Consensus::Params&) { return false; }));
|
||||||
|
|
||||||
// Attempt to validate the inputs against Heartwood. All we should learn is
|
// Attempt to validate the inputs against Heartwood. All we should learn is
|
||||||
|
@ -602,7 +606,7 @@ TEST(ContextualCheckShieldedInputsTest, JoinsplitSignatureDetectsOldBranchId) {
|
||||||
10, false, REJECT_INVALID,
|
10, false, REJECT_INVALID,
|
||||||
"bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
"bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
||||||
tx, txdata, state, orchardAuth, consensus, heartwoodBranchId, false, false,
|
tx, txdata, state, view, orchardAuth, consensus, heartwoodBranchId, false, false,
|
||||||
[](const Consensus::Params&) { return false; }));
|
[](const Consensus::Params&) { return false; }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,6 +615,9 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
|
||||||
auto consensus = Params().GetConsensus();
|
auto consensus = Params().GetConsensus();
|
||||||
auto orchardAuth = orchard::AuthValidator::Disabled();
|
auto orchardAuth = orchard::AuthValidator::Disabled();
|
||||||
|
|
||||||
|
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||||
|
CCoinsViewCache view(&baseView);
|
||||||
|
|
||||||
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
auto saplingBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_SAPLING].nBranchId;
|
||||||
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
|
CMutableTransaction mtx = GetValidTransaction(saplingBranchId);
|
||||||
|
|
||||||
|
@ -623,7 +630,7 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
|
||||||
CTransaction tx(mtx);
|
CTransaction tx(mtx);
|
||||||
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
const PrecomputedTransactionData txdata(tx, allPrevOutputs);
|
||||||
MockCValidationState state;
|
MockCValidationState state;
|
||||||
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, true));
|
EXPECT_TRUE(ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from libsodium/crypto_sign/ed25519/ref10/open.c
|
// Copied from libsodium/crypto_sign/ed25519/ref10/open.c
|
||||||
|
@ -647,15 +654,15 @@ TEST(ContextualCheckShieldedInputsTest, NonCanonicalEd25519Signature) {
|
||||||
// during initial block download, for transactions being accepted into the
|
// during initial block download, for transactions being accepted into the
|
||||||
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
// mempool (and thus not mined), DoS ban score should be zero, else 10
|
||||||
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return true; });
|
||||||
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, false, [](const Consensus::Params&) { return false; });
|
||||||
// for transactions that have been mined in a block, DoS ban score should
|
// for transactions that have been mined in a block, DoS ban score should
|
||||||
// always be 100.
|
// always be 100.
|
||||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return true; });
|
||||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false, "")).Times(1);
|
||||||
ContextualCheckShieldedInputs(tx, txdata, state, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
|
ContextualCheckShieldedInputs(tx, txdata, state, view, orchardAuth, consensus, saplingBranchId, false, true, [](const Consensus::Params&) { return false; });
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ChecktransactionTests, OverwinterConstructors) {
|
TEST(ChecktransactionTests, OverwinterConstructors) {
|
||||||
|
@ -1322,9 +1329,11 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
|
||||||
// their transparent input; ZIP 244 handles this by making the coinbase
|
// their transparent input; ZIP 244 handles this by making the coinbase
|
||||||
// sighash the txid.
|
// sighash the txid.
|
||||||
PrecomputedTransactionData txdata(tx, {});
|
PrecomputedTransactionData txdata(tx, {});
|
||||||
|
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||||
|
CCoinsViewCache view(&baseView);
|
||||||
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid", false, "")).Times(1);
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid", false, "")).Times(1);
|
||||||
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
EXPECT_FALSE(ContextualCheckShieldedInputs(
|
||||||
tx, txdata, state, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
|
tx, txdata, state, view, orchardAuth, chainparams.GetConsensus(), heartwoodBranchId, false, true));
|
||||||
|
|
||||||
RegtestDeactivateHeartwood();
|
RegtestDeactivateHeartwood();
|
||||||
}
|
}
|
||||||
|
|
74
src/main.cpp
74
src/main.cpp
|
@ -1233,6 +1233,7 @@ bool ContextualCheckShieldedInputs(
|
||||||
const CTransaction& tx,
|
const CTransaction& tx,
|
||||||
const PrecomputedTransactionData& txdata,
|
const PrecomputedTransactionData& txdata,
|
||||||
CValidationState &state,
|
CValidationState &state,
|
||||||
|
const CCoinsViewCache &view,
|
||||||
orchard::AuthValidator& orchardAuth,
|
orchard::AuthValidator& orchardAuth,
|
||||||
const Consensus::Params& consensus,
|
const Consensus::Params& consensus,
|
||||||
uint32_t consensusBranchId,
|
uint32_t consensusBranchId,
|
||||||
|
@ -1240,6 +1241,12 @@ bool ContextualCheckShieldedInputs(
|
||||||
bool isMined,
|
bool isMined,
|
||||||
bool (*isInitBlockDownload)(const Consensus::Params&))
|
bool (*isInitBlockDownload)(const Consensus::Params&))
|
||||||
{
|
{
|
||||||
|
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
|
||||||
|
// for an attacker to attempt to split the network.
|
||||||
|
if (!Consensus::CheckTxShieldedInputs(tx, state, view, 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const int DOS_LEVEL_BLOCK = 100;
|
const int DOS_LEVEL_BLOCK = 100;
|
||||||
// DoS level set to 10 to be more forgiving.
|
// DoS level set to 10 to be more forgiving.
|
||||||
const int DOS_LEVEL_MEMPOOL = 10;
|
const int DOS_LEVEL_MEMPOOL = 10;
|
||||||
|
@ -1868,16 +1875,10 @@ bool AcceptToMemoryPool(
|
||||||
return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
|
return state.Invalid(false, REJECT_DUPLICATE, "bad-txns-inputs-spent");
|
||||||
|
|
||||||
// Are the shielded spends' requirements met?
|
// Are the shielded spends' requirements met?
|
||||||
auto unmetShieldedReq = view.HaveShieldedRequirements(tx);
|
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
|
||||||
if (unmetShieldedReq) {
|
// for an attacker to attempt to split the network.
|
||||||
auto txid = tx.GetHash().ToString();
|
if (!Consensus::CheckTxShieldedInputs(tx, state, view, 0)) {
|
||||||
auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq);
|
return false;
|
||||||
auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq);
|
|
||||||
TracingError(
|
|
||||||
"main", "AcceptToMemoryPool(): shielded requirements not met",
|
|
||||||
"txid", txid.c_str(),
|
|
||||||
"reason", rejectReason.c_str());
|
|
||||||
return state.Invalid(false, rejectCode, rejectReason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bring the best block into scope
|
// Bring the best block into scope
|
||||||
|
@ -1885,6 +1886,7 @@ bool AcceptToMemoryPool(
|
||||||
|
|
||||||
nValueIn = view.GetValueIn(tx);
|
nValueIn = view.GetValueIn(tx);
|
||||||
|
|
||||||
|
// we have all inputs cached now, so switch back to dummy
|
||||||
view.SetBackend(dummy);
|
view.SetBackend(dummy);
|
||||||
|
|
||||||
// Check for non-standard pay-to-script-hash in inputs
|
// Check for non-standard pay-to-script-hash in inputs
|
||||||
|
@ -2004,6 +2006,7 @@ bool AcceptToMemoryPool(
|
||||||
tx,
|
tx,
|
||||||
txdata,
|
txdata,
|
||||||
state,
|
state,
|
||||||
|
view,
|
||||||
orchardAuth,
|
orchardAuth,
|
||||||
chainparams.GetConsensus(),
|
chainparams.GetConsensus(),
|
||||||
consensusBranchId,
|
consensusBranchId,
|
||||||
|
@ -2551,26 +2554,39 @@ int GetSpendHeight(const CCoinsViewCache& inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Consensus {
|
namespace Consensus {
|
||||||
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, const Consensus::Params& consensusParams)
|
bool CheckTxShieldedInputs(
|
||||||
|
const CTransaction& tx,
|
||||||
|
CValidationState& state,
|
||||||
|
const CCoinsViewCache& view,
|
||||||
|
int dosLevel)
|
||||||
{
|
{
|
||||||
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
|
|
||||||
// for an attacker to attempt to split the network.
|
|
||||||
if (!inputs.HaveInputs(tx))
|
|
||||||
return state.Invalid(false, 0, "", "Inputs unavailable");
|
|
||||||
|
|
||||||
// Are the shielded spends' requirements met?
|
// Are the shielded spends' requirements met?
|
||||||
auto unmetShieldedReq = inputs.HaveShieldedRequirements(tx);
|
auto unmetShieldedReq = view.HaveShieldedRequirements(tx);
|
||||||
if (unmetShieldedReq) {
|
if (unmetShieldedReq) {
|
||||||
auto txid = tx.GetHash().ToString();
|
auto txid = tx.GetHash().ToString();
|
||||||
auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq);
|
auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq);
|
||||||
auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq);
|
auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq);
|
||||||
TracingError(
|
TracingError(
|
||||||
"main", "CheckInputs(): shielded requirements not met",
|
"main", "CheckTxShieldedInputs(): shielded requirements not met",
|
||||||
"txid", txid.c_str(),
|
"txid", txid.c_str(),
|
||||||
"reason", rejectReason.c_str());
|
"reason", rejectReason.c_str());
|
||||||
return state.Invalid(false, rejectCode, rejectReason);
|
return state.DoS(dosLevel, false, rejectCode, rejectReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, const Consensus::Params& consensusParams)
|
||||||
|
{
|
||||||
|
assert(!tx.IsCoinBase());
|
||||||
|
|
||||||
|
// Indented to reduce conflicts with upstream.
|
||||||
|
{
|
||||||
|
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
|
||||||
|
// for an attacker to attempt to split the network.
|
||||||
|
if (!inputs.HaveInputs(tx))
|
||||||
|
return state.Invalid(false, 0, "", "Inputs unavailable");
|
||||||
|
|
||||||
CAmount nValueIn = 0;
|
CAmount nValueIn = 0;
|
||||||
CAmount nFees = 0;
|
CAmount nFees = 0;
|
||||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||||
|
@ -2621,6 +2637,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
|
||||||
nFees += nTxFee;
|
nFees += nTxFee;
|
||||||
if (!MoneyRange(nFees))
|
if (!MoneyRange(nFees))
|
||||||
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
|
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}// namespace Consensus
|
}// namespace Consensus
|
||||||
|
@ -3220,25 +3237,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
// txid.
|
// txid.
|
||||||
std::vector<CTxOut> allPrevOutputs;
|
std::vector<CTxOut> allPrevOutputs;
|
||||||
|
|
||||||
|
// Are the shielded spends' requirements met?
|
||||||
|
if (!Consensus::CheckTxShieldedInputs(tx, state, view, 100)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tx.IsCoinBase())
|
if (!tx.IsCoinBase())
|
||||||
{
|
{
|
||||||
if (!view.HaveInputs(tx))
|
if (!view.HaveInputs(tx))
|
||||||
return state.DoS(100, error("ConnectBlock(): inputs missing/spent"),
|
return state.DoS(100, error("ConnectBlock(): inputs missing/spent"),
|
||||||
REJECT_INVALID, "bad-txns-inputs-missingorspent");
|
REJECT_INVALID, "bad-txns-inputs-missingorspent");
|
||||||
|
|
||||||
// Are the shielded spends' requirements met?
|
|
||||||
auto unmetShieldedReq = view.HaveShieldedRequirements(tx);
|
|
||||||
if (unmetShieldedReq) {
|
|
||||||
auto txid = tx.GetHash().ToString();
|
|
||||||
auto rejectCode = ShieldedReqRejectCode(*unmetShieldedReq);
|
|
||||||
auto rejectReason = ShieldedReqRejectReason(*unmetShieldedReq);
|
|
||||||
TracingError(
|
|
||||||
"main", "ConnectBlock(): shielded requirements not met",
|
|
||||||
"txid", txid.c_str(),
|
|
||||||
"reason", rejectReason.c_str());
|
|
||||||
return state.DoS(100, false, rejectCode, rejectReason);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& input : tx.vin) {
|
for (const auto& input : tx.vin) {
|
||||||
allPrevOutputs.push_back(view.GetOutputFor(input));
|
allPrevOutputs.push_back(view.GetOutputFor(input));
|
||||||
}
|
}
|
||||||
|
@ -3303,6 +3312,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
|
||||||
tx,
|
tx,
|
||||||
txdata.back(),
|
txdata.back(),
|
||||||
state,
|
state,
|
||||||
|
view,
|
||||||
orchardAuth,
|
orchardAuth,
|
||||||
chainparams.GetConsensus(),
|
chainparams.GetConsensus(),
|
||||||
consensusBranchId,
|
consensusBranchId,
|
||||||
|
|
28
src/main.h
28
src/main.h
|
@ -373,19 +373,29 @@ bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, cons
|
||||||
std::vector<CScriptCheck> *pvChecks = NULL);
|
std::vector<CScriptCheck> *pvChecks = NULL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the signatures for a transaction's shielded components.
|
* Check whether all shielded inputs of this transaction are valid.
|
||||||
|
*
|
||||||
|
* This checks that:
|
||||||
|
* - The anchors in the transaction exist in the given view.
|
||||||
|
* - The nullifiers in the transaction do not exist in the given view.
|
||||||
|
* - The signatures for the transaction's shielded components are valid.
|
||||||
*
|
*
|
||||||
* This also currently checks the Sapling proofs, due to the way the Rust verification
|
* This also currently checks the Sapling proofs, due to the way the Rust verification
|
||||||
* code is written. Sprout and Orchard proofs are currently checked in CheckTransaction().
|
* code is written. Sprout and Orchard proofs are currently checked in CheckTransaction().
|
||||||
* Once we have batch proof validation implemented, these will all be accumulated in
|
* Once we have batch proof validation implemented, these will all be accumulated in
|
||||||
* CheckTransaction().
|
* CheckTransaction().
|
||||||
*
|
*
|
||||||
|
* To skip checking signatures, use `Consensus::CheckTxShieldedInputs` instead.
|
||||||
|
*
|
||||||
|
* This does not modify the view to add the nullifiers to the spent set.
|
||||||
|
*
|
||||||
* The `isInitBlockDownload` argument is a function parameter to assist with testing.
|
* The `isInitBlockDownload` argument is a function parameter to assist with testing.
|
||||||
*/
|
*/
|
||||||
bool ContextualCheckShieldedInputs(
|
bool ContextualCheckShieldedInputs(
|
||||||
const CTransaction& tx,
|
const CTransaction& tx,
|
||||||
const PrecomputedTransactionData& txdata,
|
const PrecomputedTransactionData& txdata,
|
||||||
CValidationState &state,
|
CValidationState &state,
|
||||||
|
const CCoinsViewCache &view,
|
||||||
orchard::AuthValidator& orchardAuth,
|
orchard::AuthValidator& orchardAuth,
|
||||||
const Consensus::Params& consensus,
|
const Consensus::Params& consensus,
|
||||||
uint32_t consensusBranchId,
|
uint32_t consensusBranchId,
|
||||||
|
@ -410,6 +420,22 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio
|
||||||
|
|
||||||
namespace Consensus {
|
namespace Consensus {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether all shielded inputs of this transaction are valid.
|
||||||
|
*
|
||||||
|
* This checks that:
|
||||||
|
* - The anchors in the transaction exist in the given view.
|
||||||
|
* - The nullifiers in the transaction do not exist in the given view.
|
||||||
|
*
|
||||||
|
* This does not modify the view to add the nullifiers to the spent set.
|
||||||
|
* This does not check proofs or signatures.
|
||||||
|
*/
|
||||||
|
bool CheckTxShieldedInputs(
|
||||||
|
const CTransaction& tx,
|
||||||
|
CValidationState& state,
|
||||||
|
const CCoinsViewCache& view,
|
||||||
|
int dosLevel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether all inputs of this transaction are valid (no double spends and amounts)
|
* Check whether all inputs of this transaction are valid (no double spends and amounts)
|
||||||
* This does not modify the UTXO set. This does not check scripts and sigs.
|
* This does not modify the UTXO set. This does not check scripts and sigs.
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "test/test_util.h"
|
#include "test/test_util.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
|
#include "utiltest.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -436,6 +437,8 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
||||||
// joinsplits.
|
// joinsplits.
|
||||||
CMutableTransaction newTx(tx);
|
CMutableTransaction newTx(tx);
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
|
AssumeShieldedInputsExistAndAreSpendable baseView;
|
||||||
|
CCoinsViewCache view(&baseView);
|
||||||
|
|
||||||
Ed25519SigningKey joinSplitPrivKey;
|
Ed25519SigningKey joinSplitPrivKey;
|
||||||
ed25519_generate_keypair(&joinSplitPrivKey, &newTx.joinSplitPubKey);
|
ed25519_generate_keypair(&joinSplitPrivKey, &newTx.joinSplitPubKey);
|
||||||
|
@ -462,7 +465,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
||||||
|
|
||||||
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
|
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
|
||||||
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
|
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
|
||||||
BOOST_CHECK(!ContextualCheckShieldedInputs(newTx, txdata, state, orchardAuth, Params().GetConsensus(), consensusBranchId, false, true));
|
BOOST_CHECK(!ContextualCheckShieldedInputs(newTx, txdata, state, view, orchardAuth, Params().GetConsensus(), consensusBranchId, false, true));
|
||||||
BOOST_CHECK(state.GetRejectReason() == "bad-txns-invalid-joinsplit-signature");
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-invalid-joinsplit-signature");
|
||||||
|
|
||||||
// Empty output script.
|
// Empty output script.
|
||||||
|
@ -478,7 +481,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
|
||||||
state = CValidationState();
|
state = CValidationState();
|
||||||
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
|
BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state));
|
||||||
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
|
BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true));
|
||||||
BOOST_CHECK(ContextualCheckShieldedInputs(newTx, txdata, state, orchardAuth, Params().GetConsensus(), consensusBranchId, false, true));
|
BOOST_CHECK(ContextualCheckShieldedInputs(newTx, txdata, state, view, orchardAuth, Params().GetConsensus(), consensusBranchId, false, true));
|
||||||
BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
|
BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
|
@ -583,42 +583,13 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The SaltedTxidHasher is fine to use here; it salts the map keys automatically
|
|
||||||
// with randomness generated on construction.
|
|
||||||
boost::unordered_map<uint256, SproutMerkleTree, SaltedTxidHasher> intermediates;
|
|
||||||
|
|
||||||
for (const JSDescription &joinsplit : tx.vJoinSplit) {
|
|
||||||
for (const uint256 &nf : joinsplit.nullifiers) {
|
|
||||||
assert(!pcoins->GetNullifier(nf, SPROUT));
|
|
||||||
}
|
|
||||||
|
|
||||||
SproutMerkleTree tree;
|
|
||||||
auto it = intermediates.find(joinsplit.anchor);
|
|
||||||
if (it != intermediates.end()) {
|
|
||||||
tree = it->second;
|
|
||||||
} else {
|
|
||||||
assert(pcoins->GetSproutAnchorAt(joinsplit.anchor, tree));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const uint256& commitment : joinsplit.commitments)
|
|
||||||
{
|
|
||||||
tree.append(commitment);
|
|
||||||
}
|
|
||||||
|
|
||||||
intermediates.insert(std::make_pair(tree.root(), tree));
|
|
||||||
}
|
|
||||||
for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
|
|
||||||
SaplingMerkleTree tree;
|
|
||||||
|
|
||||||
assert(pcoins->GetSaplingAnchorAt(spendDescription.anchor, tree));
|
|
||||||
assert(!pcoins->GetNullifier(spendDescription.nullifier, SAPLING));
|
|
||||||
}
|
|
||||||
if (fDependsWait)
|
if (fDependsWait)
|
||||||
waitingOnDependants.push_back(&(*it));
|
waitingOnDependants.push_back(&(*it));
|
||||||
else {
|
else {
|
||||||
CValidationState state;
|
CValidationState state;
|
||||||
bool fCheckResult = tx.IsCoinBase() ||
|
bool fCheckResult = tx.IsCoinBase() ||
|
||||||
Consensus::CheckTxInputs(tx, state, mempoolDuplicate, nSpendHeight, Params().GetConsensus());
|
Consensus::CheckTxInputs(tx, state, mempoolDuplicate, nSpendHeight, Params().GetConsensus());
|
||||||
|
fCheckResult &= Consensus::CheckTxShieldedInputs(tx, state, mempoolDuplicate, 0);
|
||||||
assert(fCheckResult);
|
assert(fCheckResult);
|
||||||
UpdateCoins(tx, mempoolDuplicate, 1000000);
|
UpdateCoins(tx, mempoolDuplicate, 1000000);
|
||||||
}
|
}
|
||||||
|
@ -635,6 +606,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
|
||||||
} else {
|
} else {
|
||||||
bool fCheckResult = entry->GetTx().IsCoinBase() ||
|
bool fCheckResult = entry->GetTx().IsCoinBase() ||
|
||||||
Consensus::CheckTxInputs(entry->GetTx(), state, mempoolDuplicate, nSpendHeight, Params().GetConsensus());
|
Consensus::CheckTxInputs(entry->GetTx(), state, mempoolDuplicate, nSpendHeight, Params().GetConsensus());
|
||||||
|
fCheckResult &= Consensus::CheckTxShieldedInputs(entry->GetTx(), state, mempoolDuplicate, 0);
|
||||||
assert(fCheckResult);
|
assert(fCheckResult);
|
||||||
UpdateCoins(entry->GetTx(), mempoolDuplicate, 1000000);
|
UpdateCoins(entry->GetTx(), mempoolDuplicate, 1000000);
|
||||||
stepsSinceLastRemove = 0;
|
stepsSinceLastRemove = 0;
|
||||||
|
|
|
@ -5,12 +5,36 @@
|
||||||
#ifndef ZCASH_UTILTEST_H
|
#ifndef ZCASH_UTILTEST_H
|
||||||
#define ZCASH_UTILTEST_H
|
#define ZCASH_UTILTEST_H
|
||||||
|
|
||||||
|
#include "coins.h"
|
||||||
#include "key_io.h"
|
#include "key_io.h"
|
||||||
#include "wallet/wallet.h"
|
#include "wallet/wallet.h"
|
||||||
#include "zcash/Address.hpp"
|
#include "zcash/Address.hpp"
|
||||||
#include "zcash/Note.hpp"
|
#include "zcash/Note.hpp"
|
||||||
#include "zcash/NoteEncryption.hpp"
|
#include "zcash/NoteEncryption.hpp"
|
||||||
|
|
||||||
|
// A fake chain state view where anchors and nullifiers are assumed to exist.
|
||||||
|
class AssumeShieldedInputsExistAndAreSpendable : public CCoinsView {
|
||||||
|
public:
|
||||||
|
AssumeShieldedInputsExistAndAreSpendable() {}
|
||||||
|
|
||||||
|
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetOrchardAnchorAt(const uint256 &rt, OrchardMerkleFrontier &tree) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
||||||
|
// Always return false so we treat every nullifier as being unspent.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Sprout
|
// Sprout
|
||||||
CWalletTx GetValidSproutReceive(const libzcash::SproutSpendingKey& sk,
|
CWalletTx GetValidSproutReceive(const libzcash::SproutSpendingKey& sk,
|
||||||
CAmount value,
|
CAmount value,
|
||||||
|
|
Loading…
Reference in New Issue