Throw an exception instead of asserting if Rust tx parser fails

The Rust parser is stricter than the C++ parser, so we can reach errors
now non-contextually that previously were thrown by the consensus rules.

Various tests have been updated to check for these exceptions, as they
can no longer instantiate these transactions to pass to the consensus
rules. The tests use an unsafe constructor so they can still check the
consensus rules.
This commit is contained in:
Jack Grigg 2021-06-13 00:28:25 +01:00
parent a54359eed6
commit 5ce1e649d3
5 changed files with 82 additions and 28 deletions

View File

@ -36,6 +36,14 @@ TEST(CheckBlock, VersionTooLow) {
} }
// Subclass of CTransaction which doesn't call UpdateHash when constructing
// from a CMutableTransaction. This enables us to create a CTransaction
// with bad values which normally trigger an exception during construction.
class UNSAFE_CTransaction : public CTransaction {
public:
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
};
// Test that a Sprout tx with negative version is still rejected // Test that a Sprout tx with negative version is still rejected
// by CheckBlock under Sprout consensus rules. // by CheckBlock under Sprout consensus rules.
TEST(CheckBlock, BlockSproutRejectsBadVersion) { TEST(CheckBlock, BlockSproutRejectsBadVersion) {
@ -55,7 +63,8 @@ TEST(CheckBlock, BlockSproutRejectsBadVersion) {
mtx.nVersion = -1; mtx.nVersion = -1;
mtx.nVersionGroupId = 0; mtx.nVersionGroupId = 0;
CTransaction tx {mtx}; EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx {mtx};
CBlock block; CBlock block;
block.vtx.push_back(tx); block.vtx.push_back(tx);

View File

@ -11,6 +11,14 @@
#include <librustzcash.h> #include <librustzcash.h>
#include <rust/ed25519.h> #include <rust/ed25519.h>
// Subclass of CTransaction which doesn't call UpdateHash when constructing
// from a CMutableTransaction. This enables us to create a CTransaction
// with bad values which normally trigger an exception during construction.
class UNSAFE_CTransaction : public CTransaction {
public:
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
};
TEST(ChecktransactionTests, CheckVpubNotBothNonzero) { TEST(ChecktransactionTests, CheckVpubNotBothNonzero) {
CMutableTransaction tx; CMutableTransaction tx;
tx.nVersion = 2; tx.nVersion = 2;
@ -124,7 +132,8 @@ TEST(ChecktransactionTests, BadVersionTooLow) {
CMutableTransaction mtx = GetValidTransaction(); CMutableTransaction mtx = GetValidTransaction();
mtx.nVersion = 0; mtx.nVersion = 0;
CTransaction tx(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1);
CheckTransactionWithoutProofVerification(tx, state); CheckTransactionWithoutProofVerification(tx, state);
@ -273,7 +282,8 @@ TEST(ChecktransactionTests, BadTxnsVoutNegative) {
CMutableTransaction mtx = GetValidTransaction(); CMutableTransaction mtx = GetValidTransaction();
mtx.vout[0].nValue = -1; mtx.vout[0].nValue = -1;
CTransaction tx(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative", false)).Times(1);
@ -284,7 +294,8 @@ TEST(ChecktransactionTests, BadTxnsVoutToolarge) {
CMutableTransaction mtx = GetValidTransaction(); CMutableTransaction mtx = GetValidTransaction();
mtx.vout[0].nValue = MAX_MONEY + 1; mtx.vout[0].nValue = MAX_MONEY + 1;
CTransaction tx(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge", false)).Times(1);
@ -787,7 +798,8 @@ TEST(ChecktransactionTests, SproutTxVersionTooLow) {
mtx.fOverwintered = false; mtx.fOverwintered = false;
mtx.nVersion = -1; mtx.nVersion = -1;
CTransaction tx(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-version-too-low", false)).Times(1);
CheckTransactionWithoutProofVerification(tx, state); CheckTransactionWithoutProofVerification(tx, state);
@ -795,14 +807,6 @@ TEST(ChecktransactionTests, SproutTxVersionTooLow) {
// Subclass of CTransaction which doesn't call UpdateHash when constructing
// from a CMutableTransaction. This enables us to create a CTransaction
// with bad values which normally trigger an exception during construction.
class UNSAFE_CTransaction : public CTransaction {
public:
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
};
TEST(ChecktransactionTests, SaplingSproutInputSumsTooLarge) { TEST(ChecktransactionTests, SaplingSproutInputSumsTooLarge) {
CMutableTransaction mtx = GetValidTransaction(); CMutableTransaction mtx = GetValidTransaction();
mtx.vJoinSplit.resize(0); mtx.vJoinSplit.resize(0);
@ -866,6 +870,7 @@ TEST(ChecktransactionTests, OverwinterVersionNumberLow) {
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0; mtx.nExpiryHeight = 0;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx); UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-low", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-low", false)).Times(1);
@ -884,6 +889,7 @@ TEST(ChecktransactionTests, OverwinterVersionNumberHigh) {
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nExpiryHeight = 0; mtx.nExpiryHeight = 0;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx); UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-high", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-high", false)).Times(1);
@ -903,6 +909,7 @@ TEST(ChecktransactionTests, OverwinterBadVersionGroupId) {
mtx.nExpiryHeight = 0; mtx.nExpiryHeight = 0;
mtx.nVersionGroupId = 0x12345678; mtx.nVersionGroupId = 0x12345678;
EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx); UNSAFE_CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-version-group-id", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-version-group-id", false)).Times(1);
@ -1250,7 +1257,8 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
// out of range. // out of range.
{ {
mtx.valueBalance = MAX_MONEY + 1; mtx.valueBalance = MAX_MONEY + 1;
CTransaction tx(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase()); EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state; MockCValidationState state;
@ -1259,7 +1267,8 @@ TEST(ChecktransactionTests, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
} }
{ {
mtx.valueBalance = -MAX_MONEY - 1; mtx.valueBalance = -MAX_MONEY - 1;
CTransaction tx(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase()); EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state; MockCValidationState state;

View File

@ -175,6 +175,14 @@ TEST(Mempool, SproutV3TxWhenOverwinterActive) {
} }
// Subclass of CTransaction which doesn't call UpdateHash when constructing
// from a CMutableTransaction. This enables us to create a CTransaction
// with bad values which normally trigger an exception during construction.
class UNSAFE_CTransaction : public CTransaction {
public:
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
};
// Sprout transaction with negative version, rejected by the mempool in CheckTransaction // Sprout transaction with negative version, rejected by the mempool in CheckTransaction
// under Sprout consensus rules, should still be rejected under Overwinter consensus rules. // under Sprout consensus rules, should still be rejected under Overwinter consensus rules.
// 1. fails CheckTransaction (specifically CheckTransactionWithoutProofVerification) // 1. fails CheckTransaction (specifically CheckTransactionWithoutProofVerification)
@ -198,7 +206,8 @@ TEST(Mempool, SproutNegativeVersionTxWhenOverwinterActive) {
mtx.nVersion = -3; mtx.nVersion = -3;
EXPECT_EQ(mtx.nVersion, static_cast<int32_t>(0xfffffffd)); EXPECT_EQ(mtx.nVersion, static_cast<int32_t>(0xfffffffd));
CTransaction tx1(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx1(mtx);
EXPECT_EQ(tx1.nVersion, -3); EXPECT_EQ(tx1.nVersion, -3);
CValidationState state1; CValidationState state1;
@ -215,7 +224,8 @@ TEST(Mempool, SproutNegativeVersionTxWhenOverwinterActive) {
mtx.nVersion = static_cast<int32_t>((1 << 31) | 3); mtx.nVersion = static_cast<int32_t>((1 << 31) | 3);
EXPECT_EQ(mtx.nVersion, static_cast<int32_t>(0x80000003)); EXPECT_EQ(mtx.nVersion, static_cast<int32_t>(0x80000003));
CTransaction tx1(mtx); EXPECT_THROW((CTransaction(mtx)), std::ios_base::failure);
UNSAFE_CTransaction tx1(mtx);
EXPECT_EQ(tx1.nVersion, -2147483645); EXPECT_EQ(tx1.nVersion, -2147483645);
CValidationState state1; CValidationState state1;

View File

@ -143,11 +143,14 @@ uint256 CMutableTransaction::GetHash() const
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << *this; ss << *this;
uint256 hash; uint256 hash;
assert(zcash_transaction_digests( if (!zcash_transaction_digests(
reinterpret_cast<const unsigned char*>(ss.data()), reinterpret_cast<const unsigned char*>(ss.data()),
ss.size(), ss.size(),
hash.begin(), hash.begin(),
nullptr)); nullptr))
{
throw std::ios_base::failure("CMutableTransaction::GetHash: Invalid transaction format");
}
return hash; return hash;
} }
@ -156,11 +159,14 @@ uint256 CMutableTransaction::GetAuthDigest() const
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << *this; ss << *this;
uint256 authDigest; uint256 authDigest;
assert(zcash_transaction_digests( if (!zcash_transaction_digests(
reinterpret_cast<const unsigned char*>(ss.data()), reinterpret_cast<const unsigned char*>(ss.data()),
ss.size(), ss.size(),
nullptr, nullptr,
authDigest.begin())); authDigest.begin()))
{
throw std::ios_base::failure("CMutableTransaction::GetAuthDigest: Invalid transaction format");
}
return authDigest; return authDigest;
} }
@ -168,11 +174,14 @@ void CTransaction::UpdateHash() const
{ {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << *this; ss << *this;
assert(zcash_transaction_digests( if (!zcash_transaction_digests(
reinterpret_cast<const unsigned char*>(ss.data()), reinterpret_cast<const unsigned char*>(ss.data()),
ss.size(), ss.size(),
const_cast<uint256*>(&hash)->begin(), const_cast<uint256*>(&hash)->begin(),
const_cast<uint256*>(&authDigest)->begin())); const_cast<uint256*>(&authDigest)->begin()))
{
throw std::ios_base::failure("CTransaction::UpdateHash: Invalid transaction format");
}
} }
CTransaction::CTransaction() : nVersion(CTransaction::SPROUT_MIN_CURRENT_VERSION), CTransaction::CTransaction() : nVersion(CTransaction::SPROUT_MIN_CURRENT_VERSION),

View File

@ -43,6 +43,14 @@
using namespace std; using namespace std;
// Subclass of CTransaction which doesn't call UpdateHash when constructing
// from a CMutableTransaction. This enables us to create a CTransaction
// with bad values which normally trigger an exception during construction.
class UNSAFE_CTransaction : public CTransaction {
public:
UNSAFE_CTransaction(const CMutableTransaction &tx) : CTransaction(tx, true) {}
};
BOOST_FIXTURE_TEST_SUITE(transaction_tests, JoinSplitTestingSetup) BOOST_FIXTURE_TEST_SUITE(transaction_tests, JoinSplitTestingSetup)
BOOST_AUTO_TEST_CASE(tx_valid) BOOST_AUTO_TEST_CASE(tx_valid)
@ -188,7 +196,12 @@ BOOST_AUTO_TEST_CASE(tx_invalid)
string transaction = test[1].get_str(); string transaction = test[1].get_str();
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION); CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
CTransaction tx; CTransaction tx;
try {
stream >> tx; stream >> tx;
} catch (std::ios_base::failure) {
// Invalid transaction was caught at parse time by the Rust logic.
continue;
}
CValidationState state; CValidationState state;
fValid = CheckTransaction(tx, state, verifier) && state.IsValid(); fValid = CheckTransaction(tx, state, verifier) && state.IsValid();
@ -463,23 +476,27 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa
JSDescription *jsdesc = &newTx.vJoinSplit[0]; JSDescription *jsdesc = &newTx.vJoinSplit[0];
jsdesc->vpub_old = -1; jsdesc->vpub_old = -1;
BOOST_CHECK(!CheckTransaction(newTx, state, verifier)); BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-negative"); BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-negative");
jsdesc->vpub_old = MAX_MONEY + 1; jsdesc->vpub_old = MAX_MONEY + 1;
BOOST_CHECK(!CheckTransaction(newTx, state, verifier)); BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-toolarge"); BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-toolarge");
jsdesc->vpub_old = 0; jsdesc->vpub_old = 0;
jsdesc->vpub_new = -1; jsdesc->vpub_new = -1;
BOOST_CHECK(!CheckTransaction(newTx, state, verifier)); BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-negative"); BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-negative");
jsdesc->vpub_new = MAX_MONEY + 1; jsdesc->vpub_new = MAX_MONEY + 1;
BOOST_CHECK(!CheckTransaction(newTx, state, verifier)); BOOST_CHECK_THROW((CTransaction(newTx)), std::ios_base::failure);
BOOST_CHECK(!CheckTransaction(UNSAFE_CTransaction(newTx), state, verifier));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-toolarge"); BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-toolarge");
jsdesc->vpub_new = (MAX_MONEY / 2) + 10; jsdesc->vpub_new = (MAX_MONEY / 2) + 10;