2017-11-09 19:34:51 +01:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
|
|
|
|
* Copyright (C) 2011-2015 The Bitcoin Core developers
|
2026-05-13 16:43:23 +02:00
|
|
|
* Copyright (C) 2016-2026 Tom Zander <tom@flowee.org>
|
2017-11-09 19:34:51 +01:00
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
2013-04-13 00:13:08 -05:00
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
#include "transaction_tests.h"
|
|
|
|
|
|
2024-09-01 18:49:11 +02:00
|
|
|
#include <utils/streaming/BufferPools.h>
|
|
|
|
|
|
2024-09-07 19:38:43 +02:00
|
|
|
#include "TransactionBuilder.h"
|
2013-09-10 15:18:09 -04:00
|
|
|
#include "data/tx_invalid.json.h"
|
|
|
|
|
#include "data/tx_valid.json.h"
|
2015-01-24 15:57:12 +01:00
|
|
|
#include "core_io.h"
|
2026-04-14 23:43:29 +02:00
|
|
|
#include "main.h"
|
|
|
|
|
#include "validation/Engine.h" // For checkTransaction
|
2026-05-08 16:48:15 +02:00
|
|
|
#include "validation/BlockValidation_p.h"
|
|
|
|
|
#include "validation/ValidationException.h"
|
2014-10-11 22:41:05 +00:00
|
|
|
#include "policy/policy.h"
|
2024-09-07 18:10:13 +02:00
|
|
|
#include "primitives/Block.h"
|
2026-05-14 15:01:57 +02:00
|
|
|
#include "primitives/ScriptDefines.h"
|
2019-03-11 14:47:12 +01:00
|
|
|
#include "primitives/script.h"
|
2016-09-07 19:28:04 +02:00
|
|
|
#include "transaction_utils.h"
|
2018-01-31 16:48:10 +01:00
|
|
|
#include "chainparams.h"
|
2018-02-12 14:15:24 +01:00
|
|
|
#include <SettingsDefaults.h>
|
2018-07-23 18:05:43 +02:00
|
|
|
#include <utilstrencodings.h>
|
|
|
|
|
#include <clientversion.h>
|
2022-07-06 22:50:53 +02:00
|
|
|
#include <BitcoinVersion.h>
|
2013-04-13 00:13:08 -05:00
|
|
|
|
2014-03-10 18:17:56 -04:00
|
|
|
#include <boost/algorithm/string/classification.hpp>
|
|
|
|
|
#include <boost/algorithm/string/split.hpp>
|
2015-05-10 13:35:44 +02:00
|
|
|
#include <boost/assign/list_of.hpp>
|
2014-09-20 03:13:04 +02:00
|
|
|
#include <boost/assign/list_of.hpp>
|
2026-04-14 23:43:29 +02:00
|
|
|
#include <streaming/streams.h>
|
2014-08-20 15:15:16 -04:00
|
|
|
|
2026-05-14 08:52:55 +02:00
|
|
|
#include <limits>
|
|
|
|
|
|
2015-09-04 16:11:34 +02:00
|
|
|
#include <univalue.h>
|
2013-04-13 00:13:08 -05:00
|
|
|
|
2012-08-04 18:28:49 +02:00
|
|
|
// In script_tests.cpp
|
2015-05-13 21:29:19 +02:00
|
|
|
extern UniValue read_json(const std::string& jsondata);
|
2011-07-31 20:07:53 +02:00
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
static std::map<std::string, unsigned int> mapFlagNames = boost::assign::map_list_of
|
|
|
|
|
(std::string("NONE"), (unsigned int)SCRIPT_VERIFY_NONE)
|
|
|
|
|
(std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH)
|
|
|
|
|
(std::string("STRICTENC"), (unsigned int)SCRIPT_VERIFY_STRICTENC)
|
|
|
|
|
(std::string("DERSIG"), (unsigned int)SCRIPT_VERIFY_DERSIG)
|
|
|
|
|
(std::string("LOW_S"), (unsigned int)SCRIPT_VERIFY_LOW_S)
|
|
|
|
|
(std::string("SIGPUSHONLY"), (unsigned int)SCRIPT_VERIFY_SIGPUSHONLY)
|
|
|
|
|
(std::string("MINIMALDATA"), (unsigned int)SCRIPT_VERIFY_MINIMALDATA)
|
|
|
|
|
(std::string("NULLDUMMY"), (unsigned int)SCRIPT_VERIFY_NULLDUMMY)
|
|
|
|
|
(std::string("DISCOURAGE_UPGRADABLE_NOPS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
|
|
|
|
|
(std::string("CLEANSTACK"), (unsigned int)SCRIPT_VERIFY_CLEANSTACK)
|
2016-09-22 15:06:54 +08:00
|
|
|
(std::string("NULLFAIL"), (unsigned int)SCRIPT_VERIFY_NULLFAIL)
|
2017-08-16 16:52:31 -06:00
|
|
|
(std::string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)
|
|
|
|
|
(std::string("CHECKSEQUENCEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)
|
2017-07-24 22:40:28 +02:00
|
|
|
(std::string("SIGHASH_FORKID"), (unsigned int)SCRIPT_ENABLE_SIGHASH_FORKID);
|
2014-09-20 03:13:04 +02:00
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
unsigned int TransactionTests::parseScriptFlags(const std::string &strFlags)
|
2014-09-20 03:13:04 +02:00
|
|
|
{
|
|
|
|
|
if (strFlags.empty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2014-03-10 18:17:56 -04:00
|
|
|
unsigned int flags = 0;
|
2017-08-16 16:52:31 -06:00
|
|
|
std::vector<std::string> words;
|
2014-12-19 16:50:15 -05:00
|
|
|
boost::algorithm::split(words, strFlags, boost::algorithm::is_any_of(","));
|
2014-03-10 18:17:56 -04:00
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
for (const std::string &word : words) {
|
|
|
|
|
Q_ASSERT(mapFlagNames.count(word)); // if fail; "unknown verification flag");
|
2014-03-10 18:17:56 -04:00
|
|
|
flags |= mapFlagNames[word];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return flags;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string FormatScriptFlags(unsigned int flags)
|
2014-09-20 03:13:04 +02:00
|
|
|
{
|
2018-12-28 12:10:28 +01:00
|
|
|
if (flags == 0)
|
|
|
|
|
return std::string();
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string ret;
|
|
|
|
|
std::map<std::string, unsigned int>::const_iterator it = mapFlagNames.begin();
|
2014-09-20 03:13:04 +02:00
|
|
|
while (it != mapFlagNames.end()) {
|
|
|
|
|
if (flags & it->second) {
|
|
|
|
|
ret += it->first + ",";
|
|
|
|
|
}
|
|
|
|
|
it++;
|
|
|
|
|
}
|
|
|
|
|
return ret.substr(0, ret.size() - 1);
|
|
|
|
|
}
|
|
|
|
|
|
2011-07-31 20:07:53 +02:00
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
void TransactionTests::tx_valid()
|
2012-08-04 18:28:49 +02:00
|
|
|
{
|
|
|
|
|
// Read tests from test/data/tx_valid.json
|
|
|
|
|
// Format is an array of arrays
|
|
|
|
|
// Inner arrays are either [ "comment" ]
|
2014-03-10 18:17:56 -04:00
|
|
|
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, verifyFlags
|
2012-08-04 18:28:49 +02:00
|
|
|
// ... where all scripts are stringified scripts.
|
2014-03-10 18:17:56 -04:00
|
|
|
//
|
|
|
|
|
// verifyFlags is a comma separated list of script verification flags to apply, or "NONE"
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue tests = read_json(std::string(json_tests::tx_valid, json_tests::tx_valid + sizeof(json_tests::tx_valid)));
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2014-08-20 15:15:16 -04:00
|
|
|
for (unsigned int idx = 0; idx < tests.size(); idx++) {
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue test = tests[idx];
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string strTest = test.write();
|
2019-09-04 14:11:10 +02:00
|
|
|
if (test[0].isArray()) {
|
|
|
|
|
if (test.size() != 3 || !test[1].isStr() || !test[2].isStr() || test[1].get_str().empty() || test[2].get_str().empty()) {
|
2019-10-10 19:20:04 +02:00
|
|
|
logCritical() << strTest;
|
2018-12-28 12:10:28 +01:00
|
|
|
QFAIL("Bad test");
|
2012-08-04 18:28:49 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
std::map<COutPoint, CScript> mapprevOutScriptPubKeys;
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue inputs = test[0].get_array();
|
2012-08-04 18:28:49 +02:00
|
|
|
bool fValid = true;
|
2019-09-04 14:11:10 +02:00
|
|
|
for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
|
|
|
|
|
const UniValue& input = inputs[inpIdx];
|
|
|
|
|
if (!input.isArray()) {
|
2012-08-04 18:28:49 +02:00
|
|
|
fValid = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue vinput = input.get_array();
|
2019-09-04 14:11:10 +02:00
|
|
|
if (vinput.size() != 3) {
|
2012-08-04 18:28:49 +02:00
|
|
|
fValid = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-16 14:50:05 +01:00
|
|
|
mapprevOutScriptPubKeys[COutPoint(uint256S(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str());
|
2012-08-04 18:28:49 +02:00
|
|
|
}
|
2018-12-28 12:10:28 +01:00
|
|
|
Q_ASSERT(fValid);
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string transaction = test[1].get_str();
|
2012-08-04 18:28:49 +02:00
|
|
|
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
|
|
|
|
CTransaction tx;
|
|
|
|
|
stream >> tx;
|
|
|
|
|
|
2026-05-14 12:45:20 +02:00
|
|
|
auto newTx = Tx::fromOldTransaction(tx);
|
2026-04-14 23:43:29 +02:00
|
|
|
try {
|
2026-05-14 12:45:20 +02:00
|
|
|
Validation::checkTransaction(newTx);
|
2026-04-14 23:43:29 +02:00
|
|
|
} catch (...) {
|
|
|
|
|
QFAIL("validation of tx failed");
|
|
|
|
|
}
|
2012-09-05 21:46:48 -04:00
|
|
|
|
2019-09-04 14:11:10 +02:00
|
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
2012-08-04 18:28:49 +02:00
|
|
|
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
|
2018-12-28 12:10:28 +01:00
|
|
|
QFAIL("Bad test");
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2021-01-20 19:21:53 +01:00
|
|
|
int64_t amount = 0;
|
2018-12-28 12:10:28 +01:00
|
|
|
unsigned int verify_flags = parseScriptFlags(test[2].get_str());
|
2020-04-11 18:35:43 +02:00
|
|
|
Script::State state(verify_flags);
|
|
|
|
|
const bool ok = Script::verify(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout],
|
2026-05-14 12:45:20 +02:00
|
|
|
TransactionSignatureChecker(&tx, newTx, i, amount), state);
|
2019-10-10 19:20:04 +02:00
|
|
|
if (!ok)
|
|
|
|
|
logDebug() << strTest;
|
2020-04-11 18:35:43 +02:00
|
|
|
QCOMPARE(state.errorString(), "No error");
|
2019-10-10 19:20:04 +02:00
|
|
|
QVERIFY(ok);
|
2012-08-04 18:28:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
void TransactionTests::tx_invalid()
|
2012-08-04 18:28:49 +02:00
|
|
|
{
|
|
|
|
|
// Read tests from test/data/tx_invalid.json
|
|
|
|
|
// Format is an array of arrays
|
|
|
|
|
// Inner arrays are either [ "comment" ]
|
2014-03-10 18:17:56 -04:00
|
|
|
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, verifyFlags
|
2012-08-04 18:28:49 +02:00
|
|
|
// ... where all scripts are stringified scripts.
|
2014-03-10 18:17:56 -04:00
|
|
|
//
|
|
|
|
|
// verifyFlags is a comma separated list of script verification flags to apply, or "NONE"
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue tests = read_json(std::string(json_tests::tx_invalid, json_tests::tx_invalid + sizeof(json_tests::tx_invalid)));
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2014-08-20 15:15:16 -04:00
|
|
|
for (unsigned int idx = 0; idx < tests.size(); idx++) {
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue test = tests[idx];
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string strTest = test.write();
|
2014-08-20 15:15:16 -04:00
|
|
|
if (test[0].isArray())
|
2012-08-04 18:28:49 +02:00
|
|
|
{
|
2014-08-20 15:15:16 -04:00
|
|
|
if (test.size() != 3 || !test[1].isStr() || !test[2].isStr())
|
2018-12-28 12:10:28 +01:00
|
|
|
QFAIL("Bad test");
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
std::map<COutPoint, CScript> mapprevOutScriptPubKeys;
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue inputs = test[0].get_array();
|
2012-08-04 18:28:49 +02:00
|
|
|
bool fValid = true;
|
2016-11-15 14:58:14 +01:00
|
|
|
for (unsigned int inpIdx = 0; inpIdx < inputs.size(); inpIdx++) {
|
|
|
|
|
const UniValue& input = inputs[inpIdx];
|
2014-08-20 15:15:16 -04:00
|
|
|
if (!input.isArray())
|
2012-08-04 18:28:49 +02:00
|
|
|
{
|
|
|
|
|
fValid = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-05-13 21:29:19 +02:00
|
|
|
UniValue vinput = input.get_array();
|
2012-08-04 18:28:49 +02:00
|
|
|
if (vinput.size() != 3)
|
|
|
|
|
{
|
|
|
|
|
fValid = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-16 14:50:05 +01:00
|
|
|
mapprevOutScriptPubKeys[COutPoint(uint256S(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str());
|
2012-08-04 18:28:49 +02:00
|
|
|
}
|
2018-12-28 12:10:28 +01:00
|
|
|
Q_ASSERT(fValid);
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string transaction = test[1].get_str();
|
2012-08-04 18:28:49 +02:00
|
|
|
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
|
|
|
|
CTransaction tx;
|
|
|
|
|
stream >> tx;
|
2026-05-14 12:45:20 +02:00
|
|
|
auto newTx = Tx::fromOldTransaction(tx);
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2026-04-14 23:43:29 +02:00
|
|
|
try {
|
2026-05-14 12:45:20 +02:00
|
|
|
Validation::checkTransaction(newTx);
|
2026-04-14 23:43:29 +02:00
|
|
|
} catch (...) {
|
|
|
|
|
fValid = false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 18:35:43 +02:00
|
|
|
Script::State scriptState;
|
2012-09-05 21:46:48 -04:00
|
|
|
|
2026-04-14 23:43:29 +02:00
|
|
|
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++) {
|
2012-08-04 18:28:49 +02:00
|
|
|
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
|
2018-12-28 12:10:28 +01:00
|
|
|
QFAIL("Bad test");
|
2012-08-04 18:28:49 +02:00
|
|
|
|
2021-01-20 19:21:53 +01:00
|
|
|
int64_t amount = 0;
|
2020-04-11 18:35:43 +02:00
|
|
|
scriptState.flags = parseScriptFlags(test[2].get_str());
|
|
|
|
|
fValid = Script::verify(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout],
|
2026-05-14 12:45:20 +02:00
|
|
|
TransactionSignatureChecker(&tx, newTx, i, amount), scriptState);
|
2020-04-11 18:35:43 +02:00
|
|
|
if (fValid)
|
|
|
|
|
QVERIFY(scriptState.error == SCRIPT_ERR_OK);
|
|
|
|
|
else
|
|
|
|
|
QVERIFY(scriptState.error != SCRIPT_ERR_OK);
|
2012-08-04 18:28:49 +02:00
|
|
|
}
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(!fValid);
|
2012-08-04 18:28:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-31 16:48:10 +01:00
|
|
|
static std::vector<unsigned char> getTestTx()
|
2011-07-31 20:07:53 +02:00
|
|
|
{
|
|
|
|
|
// Random real transaction (e2769b09e784f32f62ef849763d4f45b98e07ba658647343b915ff832b110436)
|
|
|
|
|
unsigned char ch[] = {0x01, 0x00, 0x00, 0x00, 0x01, 0x6b, 0xff, 0x7f, 0xcd, 0x4f, 0x85, 0x65, 0xef, 0x40, 0x6d, 0xd5, 0xd6, 0x3d, 0x4f, 0xf9, 0x4f, 0x31, 0x8f, 0xe8, 0x20, 0x27, 0xfd, 0x4d, 0xc4, 0x51, 0xb0, 0x44, 0x74, 0x01, 0x9f, 0x74, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xda, 0x0d, 0xc6, 0xae, 0xce, 0xfe, 0x1e, 0x06, 0xef, 0xdf, 0x05, 0x77, 0x37, 0x57, 0xde, 0xb1, 0x68, 0x82, 0x09, 0x30, 0xe3, 0xb0, 0xd0, 0x3f, 0x46, 0xf5, 0xfc, 0xf1, 0x50, 0xbf, 0x99, 0x0c, 0x02, 0x21, 0x00, 0xd2, 0x5b, 0x5c, 0x87, 0x04, 0x00, 0x76, 0xe4, 0xf2, 0x53, 0xf8, 0x26, 0x2e, 0x76, 0x3e, 0x2d, 0xd5, 0x1e, 0x7f, 0xf0, 0xbe, 0x15, 0x77, 0x27, 0xc4, 0xbc, 0x42, 0x80, 0x7f, 0x17, 0xbd, 0x39, 0x01, 0x41, 0x04, 0xe6, 0xc2, 0x6e, 0xf6, 0x7d, 0xc6, 0x10, 0xd2, 0xcd, 0x19, 0x24, 0x84, 0x78, 0x9a, 0x6c, 0xf9, 0xae, 0xa9, 0x93, 0x0b, 0x94, 0x4b, 0x7e, 0x2d, 0xb5, 0x34, 0x2b, 0x9d, 0x9e, 0x5b, 0x9f, 0xf7, 0x9a, 0xff, 0x9a, 0x2e, 0xe1, 0x97, 0x8d, 0xd7, 0xfd, 0x01, 0xdf, 0xc5, 0x22, 0xee, 0x02, 0x28, 0x3d, 0x3b, 0x06, 0xa9, 0xd0, 0x3a, 0xcf, 0x80, 0x96, 0x96, 0x8d, 0x7d, 0xbb, 0x0f, 0x91, 0x78, 0xff, 0xff, 0xff, 0xff, 0x02, 0x8b, 0xa7, 0x94, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xba, 0xde, 0xec, 0xfd, 0xef, 0x05, 0x07, 0x24, 0x7f, 0xc8, 0xf7, 0x42, 0x41, 0xd7, 0x3b, 0xc0, 0x39, 0x97, 0x2d, 0x7b, 0x88, 0xac, 0x40, 0x94, 0xa8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xc1, 0x09, 0x32, 0x48, 0x3f, 0xec, 0x93, 0xed, 0x51, 0xf5, 0xfe, 0x95, 0xe7, 0x25, 0x59, 0xf2, 0xcc, 0x70, 0x43, 0xf9, 0x88, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00};
|
2017-08-16 16:52:31 -06:00
|
|
|
std::vector<unsigned char> vch(ch, ch + sizeof(ch) -1);
|
2018-01-31 16:48:10 +01:00
|
|
|
return vch;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
void TransactionTests::basic_transaction_tests()
|
2018-01-31 16:48:10 +01:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
auto vch = getTestTx();
|
2012-04-17 20:37:47 +02:00
|
|
|
CDataStream stream(vch, SER_DISK, CLIENT_VERSION);
|
2014-06-07 13:53:27 +02:00
|
|
|
CMutableTransaction tx;
|
2011-07-31 20:07:53 +02:00
|
|
|
stream >> tx;
|
2026-04-14 23:43:29 +02:00
|
|
|
try {
|
|
|
|
|
// Simple deserialized transaction should be valid.
|
|
|
|
|
Validation::checkTransaction(Tx::fromOldTransaction(tx));
|
|
|
|
|
} catch (...) {
|
|
|
|
|
QFAIL("failed validation 1");
|
|
|
|
|
}
|
2011-07-31 20:07:53 +02:00
|
|
|
// Check that duplicate txins fail
|
|
|
|
|
tx.vin.push_back(tx.vin[0]);
|
2026-04-14 23:43:29 +02:00
|
|
|
try {
|
|
|
|
|
// Transaction with duplicate txins should be invalid.
|
|
|
|
|
Validation::checkTransaction(Tx::fromOldTransaction(tx));
|
|
|
|
|
QFAIL("Failed to fail validation checks");
|
|
|
|
|
} catch (...) { }
|
2011-07-31 20:07:53 +02:00
|
|
|
}
|
|
|
|
|
|
2012-01-10 20:18:00 -05:00
|
|
|
//
|
|
|
|
|
// Helper: create two dummy transactions, each with
|
2012-01-19 13:30:54 -05:00
|
|
|
// two outputs. The first has 11 and 50 CENT outputs
|
|
|
|
|
// paid to a TX_PUBKEY, the second 21 and 22 CENT outputs
|
|
|
|
|
// paid to a TX_PUBKEYHASH.
|
2012-01-10 20:18:00 -05:00
|
|
|
//
|
2024-09-07 19:38:43 +02:00
|
|
|
static std::vector<CMutableTransaction> SetupDummyInputs(std::vector<PrivateKey> &keys)
|
2012-01-10 20:18:00 -05:00
|
|
|
{
|
2014-06-07 13:53:27 +02:00
|
|
|
std::vector<CMutableTransaction> dummyTransactions;
|
2012-01-10 20:18:00 -05:00
|
|
|
dummyTransactions.resize(2);
|
|
|
|
|
|
2024-09-07 19:38:43 +02:00
|
|
|
keys.resize(4);
|
2012-01-10 20:18:00 -05:00
|
|
|
// Add some keys to the keystore:
|
2024-09-07 19:38:43 +02:00
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
keys[i].makeNewKey(i % 2);
|
2012-01-10 20:18:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create some dummy input transactions
|
|
|
|
|
dummyTransactions[0].vout.resize(2);
|
|
|
|
|
dummyTransactions[0].vout[0].nValue = 11*CENT;
|
2024-09-07 19:38:43 +02:00
|
|
|
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(keys[0].getPubKey()) << OP_CHECKSIG;
|
2012-01-10 20:18:00 -05:00
|
|
|
dummyTransactions[0].vout[1].nValue = 50*CENT;
|
2024-09-07 19:38:43 +02:00
|
|
|
dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(keys[1].getPubKey()) << OP_CHECKSIG;
|
2012-01-10 20:18:00 -05:00
|
|
|
|
|
|
|
|
dummyTransactions[1].vout.resize(2);
|
|
|
|
|
dummyTransactions[1].vout[0].nValue = 21*CENT;
|
2024-09-07 19:38:43 +02:00
|
|
|
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(keys[2].getPubKey().getKeyId());
|
2012-01-10 20:18:00 -05:00
|
|
|
dummyTransactions[1].vout[1].nValue = 22*CENT;
|
2024-09-07 19:38:43 +02:00
|
|
|
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(keys[3].getPubKey().getKeyId());
|
2012-01-10 20:18:00 -05:00
|
|
|
|
|
|
|
|
return dummyTransactions;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
void TransactionTests::test_IsStandard()
|
2012-01-10 20:18:00 -05:00
|
|
|
{
|
2014-04-23 08:05:05 +02:00
|
|
|
LOCK(cs_main);
|
2024-09-07 19:38:43 +02:00
|
|
|
std::vector<PrivateKey> keystore;
|
2018-07-23 18:05:43 +02:00
|
|
|
std::vector<CMutableTransaction> dummyTransactions = SetupDummyInputs(keystore);
|
2012-01-10 20:18:00 -05:00
|
|
|
|
2014-06-07 13:53:27 +02:00
|
|
|
CMutableTransaction t;
|
2013-04-24 18:27:00 -04:00
|
|
|
t.vin.resize(1);
|
|
|
|
|
t.vin[0].prevout.hash = dummyTransactions[0].GetHash();
|
|
|
|
|
t.vin[0].prevout.n = 1;
|
|
|
|
|
t.vin[0].scriptSig << std::vector<unsigned char>(65, 0);
|
|
|
|
|
t.vout.resize(1);
|
|
|
|
|
t.vout[0].nValue = 90*CENT;
|
2022-07-06 22:12:33 +02:00
|
|
|
PrivateKey key;
|
2022-05-11 13:46:15 +02:00
|
|
|
key.makeNewKey(true);
|
|
|
|
|
t.vout[0].scriptPubKey = GetScriptForDestination(key.getPubKey().getKeyId());
|
2013-04-24 18:27:00 -04:00
|
|
|
|
2017-08-16 16:52:31 -06:00
|
|
|
std::string reason;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2013-04-24 18:27:00 -04:00
|
|
|
|
2015-10-13 19:23:11 +02:00
|
|
|
// Check dust with default relay fee:
|
2021-01-20 19:21:53 +01:00
|
|
|
int64_t nDustThreshold = 182 * minRelayTxFee.GetFeePerK()/1000 * 3;
|
|
|
|
|
QCOMPARE(nDustThreshold, (int64_t) 546);
|
2015-10-13 19:23:11 +02:00
|
|
|
// dust:
|
|
|
|
|
t.vout[0].nValue = nDustThreshold - 1;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(!IsStandardTx(t, reason));
|
2015-10-13 19:23:11 +02:00
|
|
|
// not dust:
|
|
|
|
|
t.vout[0].nValue = nDustThreshold;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2013-04-24 18:27:00 -04:00
|
|
|
|
2015-10-13 19:23:11 +02:00
|
|
|
// Check dust with odd relay fee to verify rounding:
|
|
|
|
|
// nDustThreshold = 182 * 1234 / 1000 * 3
|
|
|
|
|
minRelayTxFee = CFeeRate(1234);
|
|
|
|
|
// dust:
|
|
|
|
|
t.vout[0].nValue = 672 - 1;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(!IsStandardTx(t, reason));
|
2015-10-13 19:23:11 +02:00
|
|
|
// not dust:
|
|
|
|
|
t.vout[0].nValue = 672;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2018-02-12 14:15:24 +01:00
|
|
|
minRelayTxFee = CFeeRate(Settings::DefaultMinRelayTxFee);
|
2015-10-13 19:23:11 +02:00
|
|
|
|
2013-04-24 18:27:00 -04:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_1;
|
2026-05-14 15:01:57 +02:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
|
|
|
|
Script::TxnOutType type;
|
|
|
|
|
int dataUsed = 0;
|
|
|
|
|
QVERIFY(IsStandard(t.vout[0].scriptPubKey, type, dataUsed));
|
|
|
|
|
QCOMPARE(type, Script::TX_SCRIPT);
|
|
|
|
|
|
|
|
|
|
t.vout[0].scriptPubKey.resize(Script::MAX_P2S_SCRIPT_SIZE + 1);
|
|
|
|
|
for (auto &byte : t.vout[0].scriptPubKey)
|
|
|
|
|
byte = static_cast<unsigned char>(OP_NOP);
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(!IsStandardTx(t, reason));
|
2026-05-14 15:01:57 +02:00
|
|
|
QCOMPARE(reason, std::string("scriptpubkey"));
|
|
|
|
|
|
|
|
|
|
t.vout[0].scriptPubKey = GetScriptForDestination(key.getPubKey().getKeyId());
|
|
|
|
|
t.vin[0].scriptSig.resize(1651);
|
|
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
|
|
|
|
t.vin[0].scriptSig.resize(MAX_SCRIPT_SIZE + 1);
|
|
|
|
|
QVERIFY(!IsStandardTx(t, reason));
|
|
|
|
|
QCOMPARE(reason, std::string("scriptsig-size"));
|
|
|
|
|
t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(65, 0);
|
2013-06-24 15:09:50 -04:00
|
|
|
|
2014-10-13 10:18:05 -04:00
|
|
|
// MAX_OP_RETURN_RELAY-byte TX_NULL_DATA (standard)
|
2018-04-16 11:02:17 +02:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN
|
|
|
|
|
<< ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"
|
|
|
|
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
|
2021-03-13 16:52:04 +01:00
|
|
|
QCOMPARE(Settings::MaxOpReturnRelay, t.vout[0].scriptPubKey.size());
|
2026-05-14 15:01:57 +02:00
|
|
|
QVERIFY2(IsStandardTx(t, reason), reason.c_str());
|
2013-06-24 15:09:50 -04:00
|
|
|
|
2021-03-13 16:52:04 +01:00
|
|
|
// Multiple op-returns.
|
|
|
|
|
t.vout.push_back(t.vout.at(0));
|
|
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN // 58 + 2 bytes
|
|
|
|
|
<< ParseHex("04678afdb0fe55482719bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38123456");
|
|
|
|
|
t.vout[1].scriptPubKey = CScript() << OP_RETURN // 103 + 3 bytes
|
|
|
|
|
<< ParseHex("04678afdb0fe55482719bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"
|
|
|
|
|
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
|
|
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
|
|
|
|
|
|
|
|
|
// limit is currently 223
|
|
|
|
|
t.vout.push_back(t.vout.at(0));
|
|
|
|
|
t.vout[2].scriptPubKey = CScript() << OP_RETURN // 57 + 2
|
|
|
|
|
<< ParseHex("04678afdb0fe55482719bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
|
|
|
|
|
QVERIFY(IsStandardTx(t, reason)); // exactly within limit.
|
|
|
|
|
// one byte over limit.
|
|
|
|
|
t.vout[2].scriptPubKey = t.vout[1].scriptPubKey;
|
|
|
|
|
QCOMPARE(IsStandardTx(t, reason), false);
|
|
|
|
|
QCOMPARE(reason, "oversize-op-return");
|
|
|
|
|
|
|
|
|
|
t.vout.resize(1);
|
2014-10-13 10:18:05 -04:00
|
|
|
// MAX_OP_RETURN_RELAY+1-byte TX_NULL_DATA (non-standard)
|
2018-04-16 11:02:17 +02:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN
|
|
|
|
|
<< ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"
|
|
|
|
|
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
|
2021-03-13 16:52:04 +01:00
|
|
|
QCOMPARE(Settings::MaxOpReturnRelay + 1, t.vout[0].scriptPubKey.size());
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(!IsStandardTx(t, reason));
|
2014-10-13 10:18:05 -04:00
|
|
|
|
|
|
|
|
// Data payload can be encoded in any way...
|
|
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("");
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2014-10-13 10:18:05 -04:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("00") << ParseHex("01");
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2014-10-13 10:18:05 -04:00
|
|
|
// OP_RESERVED *is* considered to be a PUSHDATA type opcode by IsPushOnly()!
|
|
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RESERVED << -1 << 0 << ParseHex("01") << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2014-10-13 10:18:05 -04:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << 0 << ParseHex("01") << 2 << ParseHex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2014-10-13 10:18:05 -04:00
|
|
|
|
2026-05-14 15:01:57 +02:00
|
|
|
// A short non-push OP_RETURN script is standard after Layla as P2S, not TX_NULL_DATA.
|
2014-10-13 10:18:05 -04:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN;
|
2026-05-14 15:01:57 +02:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2013-06-24 15:09:50 -04:00
|
|
|
|
2013-10-24 04:32:35 -04:00
|
|
|
// TX_NULL_DATA w/o PUSHDATA
|
|
|
|
|
t.vout.resize(1);
|
|
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN;
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2013-10-24 04:32:35 -04:00
|
|
|
|
2026-05-14 15:01:57 +02:00
|
|
|
// Multiple TX_NULL_DATA outputs are standard when the aggregate size is within the relay limit.
|
2013-06-24 15:09:50 -04:00
|
|
|
t.vout.resize(2);
|
2026-05-14 15:01:57 +02:00
|
|
|
t.vout[1].nValue = 0;
|
2014-02-26 12:58:08 -05:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
|
|
|
|
|
t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
|
2026-05-14 15:01:57 +02:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2013-10-24 04:32:35 -04:00
|
|
|
|
2014-02-26 12:58:08 -05:00
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38");
|
2013-10-24 04:32:35 -04:00
|
|
|
t.vout[1].scriptPubKey = CScript() << OP_RETURN;
|
2026-05-14 15:01:57 +02:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2013-10-24 04:32:35 -04:00
|
|
|
|
|
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_RETURN;
|
|
|
|
|
t.vout[1].scriptPubKey = CScript() << OP_RETURN;
|
2026-05-14 15:01:57 +02:00
|
|
|
QVERIFY(IsStandardTx(t, reason));
|
2012-01-10 20:18:00 -05:00
|
|
|
}
|
|
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
void TransactionTests::transactionIter()
|
2018-01-31 16:48:10 +01:00
|
|
|
{
|
|
|
|
|
auto vch = getTestTx();
|
|
|
|
|
|
|
|
|
|
Streaming::BufferPool pl(vch.size());
|
|
|
|
|
memcpy(pl.begin(), vch.data(), vch.size());
|
|
|
|
|
Tx tx(pl.commit(vch.size()));
|
|
|
|
|
|
|
|
|
|
auto iter = Tx::Iterator(tx);
|
|
|
|
|
auto type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::TxVersion);
|
|
|
|
|
QCOMPARE(iter.intData(), 1);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::PrevTxHash);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 32);
|
2018-01-31 16:48:10 +01:00
|
|
|
auto prev = iter.uint256Data();
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(prev == uint256S("0xb4749f017444b051c44dfd2720e88f314ff94f3dd6d56d40ef65854fcd7fff6b"));
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::PrevTxIndex);
|
|
|
|
|
QCOMPARE(iter.intData(), 0);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::TxInScript);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 140);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::Sequence);
|
|
|
|
|
QCOMPARE(iter.uintData(), 0xffffffff);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 0xffffffff);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::OutputValue);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 244623243);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::OutputScript);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 25);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::OutputValue);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 44602432);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::OutputScript);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 25);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::LockTime);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::End);
|
2018-01-31 16:48:10 +01:00
|
|
|
}
|
|
|
|
|
|
2018-12-28 12:10:28 +01:00
|
|
|
void TransactionTests::transactionIter2()
|
2018-01-31 16:48:10 +01:00
|
|
|
{
|
|
|
|
|
// coinbase-tx
|
2026-04-15 21:35:08 +02:00
|
|
|
Block genesisBlock = Params(CBaseChainParams::MAIN).GenesisBlock();
|
|
|
|
|
QCOMPARE(genesisBlock.size(), 285);
|
2018-01-31 16:48:10 +01:00
|
|
|
auto iter = Tx::Iterator(genesisBlock);
|
|
|
|
|
auto type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::TxVersion);
|
|
|
|
|
QCOMPARE(iter.intData(), 1);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::PrevTxHash);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 32);
|
2018-01-31 16:48:10 +01:00
|
|
|
auto prev = iter.uint256Data();
|
2018-12-28 12:10:28 +01:00
|
|
|
QVERIFY(prev == uint256S("0x0000000000000000000000000000000000000000000000000000000000000000"));
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::PrevTxIndex);
|
|
|
|
|
QCOMPARE(iter.intData(), -1);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::TxInScript);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 77);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::Sequence);
|
|
|
|
|
QCOMPARE(iter.uintData(), 0xffffffff);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 0xffffffff);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::OutputValue);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 50 * COIN);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::OutputScript);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 67);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::LockTime);
|
|
|
|
|
QCOMPARE(iter.intData(), 0);
|
2018-01-31 16:48:10 +01:00
|
|
|
type = iter.next();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(type, Tx::End);
|
2018-01-31 16:48:10 +01:00
|
|
|
|
|
|
|
|
Tx tx = iter.prevTx();
|
2026-04-15 21:35:08 +02:00
|
|
|
QCOMPARE(tx.size(), 204);
|
|
|
|
|
logFatal() << tx.createHash();
|
|
|
|
|
QVERIFY(tx.createHash() == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
|
2018-01-31 16:48:10 +01:00
|
|
|
|
|
|
|
|
genesisBlock.findTransactions();
|
2018-12-28 12:10:28 +01:00
|
|
|
QCOMPARE(genesisBlock.transactions().size(), (size_t) 1);
|
2026-04-15 21:35:08 +02:00
|
|
|
QCOMPARE(genesisBlock.transactions().front().size(), 204);
|
|
|
|
|
QVERIFY(genesisBlock.transactions().front().createHash() == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"));
|
2018-01-31 16:48:10 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-01 18:49:11 +02:00
|
|
|
void TransactionTests::txIterWithTokens()
|
|
|
|
|
{
|
|
|
|
|
// we test the first ever cashtoken transaction that has landed on mainchain.
|
|
|
|
|
// TxId: 2edbe1d87de2a93f26cf4764342f67b0fdba51f05ae3cc067c1ac3746454afcb
|
|
|
|
|
auto pool = Streaming::pool(350);
|
|
|
|
|
pool->writeHex("010000000113232ccb0acabfaf3b2936814a9ab60d5218ff2105bb06099a9ab120ea2ee183000000006441647ee7a32d8d1cf46ac15e61ac48b79300e57f107a4e2d1ae4478ac4e8c180a70ed87dbb0bf00c230100191ddf779a5912c14cd7e33f0d27e05f0d4d7e5743cf412103eaa57987b59cb1e2d31aa3daa9a746c26b6016317000749486e06c3048c86528feffffff037364557e000000001976a91464efa2b10c9c167323ef39a4f1a40d167400447288ac200300000000000045ef13232ccb0acabfaf3b2936814a9ab60d5218ff2105bb06099a9ab120ea2ee1837204b33ff00dfe00752b7d76a91464efa2b10c9c167323ef39a4f1a40d167400447288ac00000000000000004c6a0442434d5220a8dd9f3f77efec7d4c159cbd28f6181e704dd0c298baea0cc3488cd1a176dc912463332d736f66742e636f6d2f746f6b656e732f66616c6c6f75745f636f696e2e6a736f6ec3180c00");
|
|
|
|
|
Tx tx(pool->commit());
|
|
|
|
|
QCOMPARE(tx.size(), 348);
|
|
|
|
|
|
|
|
|
|
auto iter = Tx::Iterator(tx);
|
|
|
|
|
auto type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::TxVersion);
|
|
|
|
|
QCOMPARE(iter.intData(), 1);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::PrevTxHash);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::PrevTxIndex);
|
|
|
|
|
QCOMPARE(iter.intData(), 0);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::TxInScript);
|
|
|
|
|
QCOMPARE(iter.byteData().size(), 100);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::Sequence);
|
|
|
|
|
|
|
|
|
|
// output one is boringly normal.
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::OutputValue);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 2119525491);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::OutputScript);
|
|
|
|
|
QCOMPARE(iter.dataLength(), 25);
|
|
|
|
|
|
|
|
|
|
// output two is a token.
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::OutputValue);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 800);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::CashTokenCategory);
|
|
|
|
|
QCOMPARE(iter.uint256Data(),
|
|
|
|
|
uint256S("83e12eea20b19a9a0906bb0521ff18520db69a4a8136293bafbfca0acb2c2313"));
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::CashTokenBitfield);
|
|
|
|
|
QCOMPARE(iter.dataLength(), 1);
|
2024-09-06 22:48:19 +02:00
|
|
|
auto bits = iter.bitfieldData();
|
|
|
|
|
QVERIFY(bits & Tx::HasNft);
|
|
|
|
|
QVERIFY(bits & Tx::HasCommitment);
|
|
|
|
|
QVERIFY(bits & Tx::HasFtAmount);
|
|
|
|
|
QVERIFY(bits & Tx::NftMintBaton);
|
|
|
|
|
QVERIFY((bits & Tx::NftMutableBaton) == 0);
|
|
|
|
|
QCOMPARE(bits, 114);
|
2024-09-01 18:49:11 +02:00
|
|
|
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::CashTokenCommitment);
|
|
|
|
|
QCOMPARE(iter.dataLength(), 4);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::CashTokenAmount);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 2100000000);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::OutputScript);
|
|
|
|
|
QCOMPARE(iter.dataLength(), 25);
|
|
|
|
|
|
|
|
|
|
// 3rd and last output is an op-return (bcmr)
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::OutputValue);
|
|
|
|
|
QCOMPARE(iter.longData(), (uint64_t) 0);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::OutputScript);
|
|
|
|
|
QCOMPARE(iter.dataLength(), 76);
|
|
|
|
|
auto bytes = iter.byteData();
|
|
|
|
|
QCOMPARE(bytes[0], 106); // op-return
|
|
|
|
|
QCOMPARE(bytes[1], 4); // magic marker part one.
|
|
|
|
|
// magic marker part 2.
|
|
|
|
|
QCOMPARE(std::string(bytes.begin() + 2, 4), std::string("BCMR"));
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::LockTime);
|
|
|
|
|
type = iter.next();
|
|
|
|
|
QCOMPARE(type, Tx::End);
|
2024-09-01 20:02:21 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
auto iter2 = Tx::Iterator(tx);
|
2026-05-13 16:43:23 +02:00
|
|
|
auto output1 = iter2.nextOutput();
|
|
|
|
|
QCOMPARE(output1.hasToken(), false);
|
2024-09-01 20:02:21 +02:00
|
|
|
QCOMPARE(output1.outputValue, (int64_t) 2119525491);
|
2026-05-13 16:43:23 +02:00
|
|
|
QCOMPARE(output1.outputScript().size(), 25);
|
|
|
|
|
auto output2 = iter2.nextOutput();
|
|
|
|
|
QCOMPARE(output2.hasToken(), true);
|
2024-09-01 20:02:21 +02:00
|
|
|
QCOMPARE(output2.outputValue, (int64_t) 800);
|
2026-05-13 16:43:23 +02:00
|
|
|
QCOMPARE(output2.outputScript().size(), 25);
|
2026-05-14 08:09:17 +02:00
|
|
|
QCOMPARE(tx.output(1).outputValue, (int64_t) 800);
|
|
|
|
|
auto skippedOutput = Tx::Iterator(tx).nextOutput(1);
|
|
|
|
|
QCOMPARE(skippedOutput.outputValue, (int64_t) 800);
|
2026-05-13 16:43:23 +02:00
|
|
|
auto output3 = iter2.nextOutput();
|
|
|
|
|
QCOMPARE(output3.hasToken(), false);
|
2024-09-01 20:02:21 +02:00
|
|
|
QCOMPARE(output3.outputValue, (int64_t) 0);
|
2026-05-13 16:43:23 +02:00
|
|
|
QCOMPARE(output3.outputScript().size(), 76);
|
|
|
|
|
QCOMPARE(output3.outputScript(), bytes);
|
|
|
|
|
auto end = iter2.nextOutput();
|
2024-09-01 20:02:21 +02:00
|
|
|
QCOMPARE(end.outputValue, (int64_t) -1);
|
2026-05-13 16:43:23 +02:00
|
|
|
|
|
|
|
|
auto token = output2.token();
|
|
|
|
|
QCOMPARE(token.category, uint256S("83e12eea20b19a9a0906bb0521ff18520db69a4a8136293bafbfca0acb2c2313"));
|
|
|
|
|
QCOMPARE(token.amount, 2100000000);
|
|
|
|
|
QCOMPARE(token.commitment.size(), 4);
|
|
|
|
|
QCOMPARE(token.hasAmount(), true);
|
|
|
|
|
QCOMPARE(token.hasNft(), true);
|
|
|
|
|
QCOMPARE(token.hasCommitment(), true);
|
|
|
|
|
QCOMPARE(token.isImmutableNft(), false);
|
|
|
|
|
QCOMPARE(token.isMutableNft(), false);
|
|
|
|
|
QCOMPARE(token.isMintingNft(), true);
|
2024-09-01 18:49:11 +02:00
|
|
|
}
|
2026-05-08 16:48:15 +02:00
|
|
|
|
2026-05-08 23:38:26 +02:00
|
|
|
static Tx makeTransactionOfSize(int size, int version = CTransaction::CURRENT_VERSION)
|
2026-05-08 16:48:15 +02:00
|
|
|
{
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
|
tx.nVersion = version;
|
|
|
|
|
tx.vin.resize(1);
|
|
|
|
|
tx.vin[0].prevout = COutPoint(uint256S("01"), 0);
|
|
|
|
|
tx.vout.resize(1);
|
|
|
|
|
tx.vout[0].scriptPubKey = CScript() << OP_TRUE;
|
|
|
|
|
|
|
|
|
|
int txSize = static_cast<int>(::GetSerializeSize(CTransaction(tx), SER_NETWORK, PROTOCOL_VERSION));
|
|
|
|
|
if (txSize > size)
|
|
|
|
|
throw std::runtime_error("test transaction template is larger than requested size");
|
|
|
|
|
tx.vin[0].scriptSig.resize(size - txSize);
|
2026-05-08 23:38:26 +02:00
|
|
|
return Tx::fromOldTransaction(tx);
|
2026-05-08 16:48:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void expectContextualTxReject(const Tx &tx, ValidationFlags flags, const std::string &reason)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx, flags);
|
2026-05-14 10:53:55 +02:00
|
|
|
QFAIL("transaction was expected to be rejected");
|
2026-05-08 16:48:15 +02:00
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), reason);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:10:38 +02:00
|
|
|
static CScript tokenOutputScript(const uint256 &category, uint8_t bitfield, uint64_t amount = 0,
|
|
|
|
|
const std::vector<uint8_t> &commitment = {})
|
|
|
|
|
{
|
|
|
|
|
CScript script;
|
|
|
|
|
script.push_back(0xef);
|
|
|
|
|
script.insert(script.end(), category.begin(), category.end());
|
|
|
|
|
script.push_back(bitfield);
|
|
|
|
|
if (bitfield & Tx::HasCommitment) {
|
|
|
|
|
script.push_back(static_cast<unsigned char>(commitment.size()));
|
|
|
|
|
script.insert(script.end(), commitment.begin(), commitment.end());
|
|
|
|
|
}
|
2026-05-14 08:52:55 +02:00
|
|
|
if (bitfield & Tx::HasFtAmount) {
|
|
|
|
|
if (amount < 253) {
|
|
|
|
|
script.push_back(static_cast<unsigned char>(amount));
|
|
|
|
|
} else if (amount <= std::numeric_limits<uint16_t>::max()) {
|
|
|
|
|
script.push_back(253);
|
|
|
|
|
script.push_back(static_cast<unsigned char>(amount));
|
|
|
|
|
script.push_back(static_cast<unsigned char>(amount >> 8));
|
|
|
|
|
} else if (amount <= std::numeric_limits<uint32_t>::max()) {
|
|
|
|
|
script.push_back(254);
|
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
|
|
|
script.push_back(static_cast<unsigned char>(amount >> (8 * i)));
|
|
|
|
|
} else {
|
|
|
|
|
script.push_back(255);
|
|
|
|
|
for (int i = 0; i < 8; ++i)
|
|
|
|
|
script.push_back(static_cast<unsigned char>(amount >> (8 * i)));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-14 08:10:38 +02:00
|
|
|
script << OP_TRUE;
|
|
|
|
|
return script;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Tx txWithOutputScript(const CScript &outputScript, bool coinbase = false)
|
|
|
|
|
{
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
|
tx.nVersion = CTransaction::CURRENT_VERSION;
|
|
|
|
|
tx.vin.resize(1);
|
|
|
|
|
if (coinbase) {
|
|
|
|
|
tx.vin[0].prevout.SetNull();
|
|
|
|
|
tx.vin[0].scriptSig = CScript() << OP_TRUE;
|
|
|
|
|
} else {
|
|
|
|
|
tx.vin[0].prevout = COutPoint(uint256S("01"), 0);
|
|
|
|
|
}
|
|
|
|
|
tx.vout.resize(1);
|
|
|
|
|
tx.vout[0].nValue = 1000;
|
|
|
|
|
tx.vout[0].scriptPubKey = outputScript;
|
|
|
|
|
return Tx::fromOldTransaction(CTransaction(tx));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:52:55 +02:00
|
|
|
static Tx txWithOutputScriptAndInputPadding(const CScript &outputScript)
|
|
|
|
|
{
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
|
tx.nVersion = CTransaction::CURRENT_VERSION;
|
|
|
|
|
tx.vin.resize(1);
|
|
|
|
|
tx.vin[0].prevout = COutPoint(uint256S("01"), 0);
|
|
|
|
|
tx.vin[0].scriptSig.resize(80);
|
|
|
|
|
tx.vout.resize(1);
|
|
|
|
|
tx.vout[0].nValue = 1000;
|
|
|
|
|
tx.vout[0].scriptPubKey = outputScript;
|
|
|
|
|
return Tx::fromOldTransaction(CTransaction(tx));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:10:38 +02:00
|
|
|
void TransactionTests::may2023_cashtoken_output_structure_rules()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
|
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category, Tx::HasFtAmount, 1)),
|
|
|
|
|
hf2023Flags);
|
|
|
|
|
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasFtAmount, 1), true),
|
|
|
|
|
hf2023Flags, "bad-txns-coinbase-has-tokens");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, 0x80)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-bad-bitfield");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasFtAmount, 0)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-non-nft-amount-zero");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-commitment-bitfield-mismatch");
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:52:55 +02:00
|
|
|
void TransactionTests::may2023_cashtoken_output_structure_edge_cases()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
2026-05-14 15:01:57 +02:00
|
|
|
ValidationFlags hf2026Flags = hf2023Flags;
|
|
|
|
|
hf2026Flags.hf202605Active = true;
|
2026-05-14 08:52:55 +02:00
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category, Tx::HasNft)),
|
|
|
|
|
hf2023Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton)),
|
|
|
|
|
hf2023Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | Tx::NftMintBaton)),
|
|
|
|
|
hf2023Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category,
|
|
|
|
|
Tx::HasNft | Tx::HasCommitment | Tx::HasFtAmount, 253, {0x01, 0x02})),
|
|
|
|
|
hf2023Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category, Tx::HasFtAmount,
|
|
|
|
|
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()))),
|
|
|
|
|
hf2023Flags);
|
|
|
|
|
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, 0)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-bad-bitfield");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasFtAmount | Tx::NftMutableBaton, 1)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-bad-bitfield");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | 3)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-bad-bitfield");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasCommitment | Tx::HasFtAmount, 1, {0x01})),
|
|
|
|
|
hf2023Flags, "bad-txns-token-bad-bitfield");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | Tx::HasFtAmount, 0)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-amount-bitfield-mismatch");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasFtAmount,
|
|
|
|
|
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1)),
|
|
|
|
|
hf2023Flags, "bad-txns-token-amount-outofrange");
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment,
|
|
|
|
|
0, std::vector<uint8_t>(41, 0x01))),
|
|
|
|
|
hf2023Flags, "bad-txns-token-commitment-oversized");
|
2026-05-14 15:01:57 +02:00
|
|
|
ValidationPrivate::checkContextualTransaction(txWithOutputScript(tokenOutputScript(category,
|
|
|
|
|
Tx::HasNft | Tx::HasCommitment, 0, std::vector<uint8_t>(128, 0x01))),
|
|
|
|
|
hf2026Flags);
|
|
|
|
|
expectContextualTxReject(txWithOutputScript(tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment,
|
|
|
|
|
0, std::vector<uint8_t>(129, 0x01))),
|
|
|
|
|
hf2026Flags, "bad-txns-token-commitment-oversized");
|
2026-05-14 08:52:55 +02:00
|
|
|
|
|
|
|
|
CScript shortPrefix;
|
|
|
|
|
shortPrefix.push_back(0xef);
|
|
|
|
|
shortPrefix << OP_TRUE;
|
2026-05-14 10:53:55 +02:00
|
|
|
expectContextualTxReject(txWithOutputScriptAndInputPadding(shortPrefix), hf2023Flags, "bad-tx-data");
|
2026-05-14 08:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:11:19 +02:00
|
|
|
static CTransaction tokenInputSpendTx()
|
|
|
|
|
{
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
|
tx.nVersion = CTransaction::CURRENT_VERSION;
|
|
|
|
|
tx.vin.resize(1);
|
|
|
|
|
tx.vin[0].prevout = COutPoint(uint256S("01"), 1);
|
|
|
|
|
tx.vout.resize(1);
|
|
|
|
|
tx.vout[0].nValue = 900;
|
|
|
|
|
tx.vout[0].scriptPubKey = CScript() << OP_TRUE;
|
|
|
|
|
return CTransaction(tx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:12:13 +02:00
|
|
|
static CTransaction tokenSpendTx(const std::vector<COutPoint> &prevouts, const std::vector<CScript> &outputScripts)
|
|
|
|
|
{
|
|
|
|
|
CMutableTransaction tx;
|
|
|
|
|
tx.nVersion = CTransaction::CURRENT_VERSION;
|
|
|
|
|
tx.vin.resize(prevouts.size());
|
|
|
|
|
for (size_t i = 0; i < prevouts.size(); ++i)
|
|
|
|
|
tx.vin[i].prevout = prevouts.at(i);
|
|
|
|
|
tx.vout.resize(outputScripts.size());
|
|
|
|
|
for (size_t i = 0; i < outputScripts.size(); ++i) {
|
|
|
|
|
tx.vout[i].nValue = 100;
|
|
|
|
|
tx.vout[i].scriptPubKey = outputScripts.at(i);
|
|
|
|
|
}
|
|
|
|
|
return CTransaction(tx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:11:19 +02:00
|
|
|
static UnspentOutputData tokenPrevout(const CScript &outputScript, int blockHeight)
|
|
|
|
|
{
|
|
|
|
|
UnspentOutputData out(Streaming::bufferFrom(outputScript), 1000);
|
|
|
|
|
out.blockHeight = blockHeight;
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:12:13 +02:00
|
|
|
static UnspentOutputData plainPrevout(int blockHeight)
|
|
|
|
|
{
|
|
|
|
|
return tokenPrevout(CScript() << OP_TRUE, blockHeight);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 12:07:30 +02:00
|
|
|
static void expectTokenInputReject(const CTransaction tx, const Tx &newTx, const std::vector<UnspentOutputData> &unspents,
|
2026-05-14 08:11:19 +02:00
|
|
|
ValidationFlags flags, const std::string &reason)
|
|
|
|
|
{
|
|
|
|
|
int64_t fees = 0;
|
|
|
|
|
uint32_t sigChecks = 0;
|
|
|
|
|
bool spendsCoinbase = false;
|
|
|
|
|
try {
|
2026-05-14 12:07:30 +02:00
|
|
|
ValidationPrivate::validateTransactionInputs(tx, newTx, unspents, Params().GetConsensus().hf202305Height,
|
2026-05-14 08:11:19 +02:00
|
|
|
flags, fees, sigChecks, spendsCoinbase, false);
|
2026-05-14 10:53:55 +02:00
|
|
|
QFAIL("transaction inputs were expected to be rejected");
|
2026-05-14 08:11:19 +02:00
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), reason);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TransactionTests::may2023_cashtoken_input_structure_rules()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags beforeMay2023;
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
|
|
|
|
const int activationHeight = Params().GetConsensus().hf202305Height;
|
|
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
std::vector<UnspentOutputData> unspents;
|
|
|
|
|
unspents.push_back(tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 1), activationHeight));
|
|
|
|
|
|
|
|
|
|
int64_t fees = 0;
|
|
|
|
|
uint32_t sigChecks = 0;
|
|
|
|
|
bool spendsCoinbase = false;
|
2026-05-14 13:33:13 +02:00
|
|
|
const auto old = tokenInputSpendTx();
|
|
|
|
|
auto tx = Tx::fromOldTransaction(old);
|
2026-05-14 12:07:30 +02:00
|
|
|
ValidationPrivate::validateTransactionInputs(old, tx, unspents, activationHeight,
|
2026-05-14 08:11:19 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
QCOMPARE(fees, static_cast<int64_t>(100));
|
|
|
|
|
|
|
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(category, 0x80), activationHeight);
|
2026-05-14 12:07:30 +02:00
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-bad-bitfield");
|
2026-05-14 08:11:19 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:12:13 +02:00
|
|
|
void TransactionTests::may2023_cashtoken_amount_accounting_rules()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
|
|
|
|
const int activationHeight = Params().GetConsensus().hf202305Height;
|
|
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
const uint256 otherCategory = uint256S("0x0000000000000000000000000000000000000000000000000000000000005678");
|
|
|
|
|
|
|
|
|
|
std::vector<UnspentOutputData> unspents;
|
|
|
|
|
unspents.push_back(tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 100), activationHeight));
|
|
|
|
|
|
|
|
|
|
auto validSpend = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasFtAmount, 70)});
|
2026-05-14 12:07:30 +02:00
|
|
|
auto tx = Tx::fromOldTransaction(validSpend);
|
2026-05-14 08:12:13 +02:00
|
|
|
int64_t fees = 0;
|
|
|
|
|
uint32_t sigChecks = 0;
|
|
|
|
|
bool spendsCoinbase = false;
|
2026-05-14 12:07:30 +02:00
|
|
|
ValidationPrivate::validateTransactionInputs(validSpend, tx, unspents, activationHeight,
|
2026-05-14 08:12:13 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
QCOMPARE(fees, static_cast<int64_t>(900));
|
|
|
|
|
|
2026-05-14 12:07:30 +02:00
|
|
|
auto old = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasFtAmount, 101)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-in-belowout");
|
|
|
|
|
|
|
|
|
|
old = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(otherCategory, Tx::HasFtAmount, 1)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-invalid-category");
|
2026-05-14 08:12:13 +02:00
|
|
|
|
|
|
|
|
unspents[0] = plainPrevout(activationHeight);
|
|
|
|
|
auto genesis = tokenSpendTx({COutPoint(otherCategory, 0)}, {tokenOutputScript(otherCategory, Tx::HasFtAmount, 10)});
|
2026-05-14 12:07:30 +02:00
|
|
|
tx = Tx::fromOldTransaction(genesis);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(genesis, tx, unspents, activationHeight,
|
2026-05-14 08:12:13 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
|
|
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(otherCategory, Tx::HasFtAmount, 10), activationHeight);
|
2026-05-14 12:07:30 +02:00
|
|
|
old = tokenSpendTx({COutPoint(otherCategory, 0)},
|
|
|
|
|
{tokenOutputScript(otherCategory, Tx::HasFtAmount, 1)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-dupe-genesis");
|
2026-05-14 08:12:13 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:52:55 +02:00
|
|
|
void TransactionTests::may2023_cashtoken_amount_accounting_edge_cases()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
|
|
|
|
const int activationHeight = Params().GetConsensus().hf202305Height;
|
|
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
|
|
|
|
|
std::vector<UnspentOutputData> unspents;
|
|
|
|
|
unspents.push_back(tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 60), activationHeight));
|
|
|
|
|
unspents.push_back(tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 40), activationHeight));
|
|
|
|
|
|
|
|
|
|
auto splitSpend = tokenSpendTx({COutPoint(uint256S("01"), 1), COutPoint(uint256S("02"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasFtAmount, 1),
|
|
|
|
|
tokenOutputScript(category, Tx::HasFtAmount, 99)});
|
|
|
|
|
int64_t fees = 0;
|
|
|
|
|
uint32_t sigChecks = 0;
|
|
|
|
|
bool spendsCoinbase = false;
|
2026-05-14 12:07:30 +02:00
|
|
|
auto tx = Tx::fromOldTransaction(splitSpend);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(splitSpend, tx, unspents, activationHeight,
|
2026-05-14 08:52:55 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
QCOMPARE(fees, static_cast<int64_t>(1800));
|
|
|
|
|
|
|
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount,
|
|
|
|
|
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())), activationHeight);
|
|
|
|
|
unspents[1] = tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 1), activationHeight);
|
2026-05-14 12:07:30 +02:00
|
|
|
|
|
|
|
|
auto old = tokenSpendTx({COutPoint(uint256S("01"), 1), COutPoint(uint256S("02"), 1)}, {CScript() << OP_TRUE});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-amount-overflow");
|
2026-05-14 08:52:55 +02:00
|
|
|
|
|
|
|
|
unspents = {tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 100), activationHeight)};
|
2026-05-14 12:07:30 +02:00
|
|
|
old = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasFtAmount, 50),
|
|
|
|
|
tokenOutputScript(category, Tx::HasFtAmount, 51)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-in-belowout");
|
2026-05-14 08:52:55 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:12:58 +02:00
|
|
|
void TransactionTests::may2023_cashtoken_nft_accounting_rules()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
|
|
|
|
const int activationHeight = Params().GetConsensus().hf202305Height;
|
|
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
const std::vector<uint8_t> commitmentA = {0xaa};
|
|
|
|
|
const std::vector<uint8_t> commitmentB = {0xbb};
|
|
|
|
|
|
|
|
|
|
std::vector<UnspentOutputData> unspents;
|
|
|
|
|
unspents.push_back(tokenPrevout(tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentA),
|
|
|
|
|
activationHeight));
|
|
|
|
|
int64_t fees = 0;
|
|
|
|
|
uint32_t sigChecks = 0;
|
|
|
|
|
bool spendsCoinbase = false;
|
|
|
|
|
|
|
|
|
|
auto immutableTransfer = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
2026-05-14 12:07:30 +02:00
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentA)});
|
|
|
|
|
auto tx = Tx::fromOldTransaction(immutableTransfer);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(immutableTransfer, tx, unspents, activationHeight,
|
2026-05-14 08:12:58 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
2026-05-14 12:07:30 +02:00
|
|
|
auto old = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentB)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-nft-ex-nihilo");
|
2026-05-14 08:12:58 +02:00
|
|
|
|
|
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton), activationHeight);
|
|
|
|
|
auto mutableToImmutable = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
2026-05-14 12:07:30 +02:00
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentB)});
|
|
|
|
|
tx = Tx::fromOldTransaction(mutableToImmutable);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(mutableToImmutable, tx, unspents, activationHeight,
|
2026-05-14 08:12:58 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
2026-05-14 12:07:30 +02:00
|
|
|
old = tokenSpendTx({COutPoint(uint256S("01"), 1)}, {tokenOutputScript(category, Tx::HasNft | Tx::NftMintBaton)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-nft-ex-nihilo");
|
|
|
|
|
old = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton),
|
|
|
|
|
tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-nft-ex-nihilo");
|
2026-05-14 08:12:58 +02:00
|
|
|
|
|
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(category, Tx::HasNft | Tx::NftMintBaton), activationHeight);
|
|
|
|
|
auto mintingCreatesMany = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
2026-05-14 12:07:30 +02:00
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::NftMintBaton),
|
|
|
|
|
tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton),
|
|
|
|
|
tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentB)});
|
|
|
|
|
tx = Tx::fromOldTransaction(mintingCreatesMany);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(mintingCreatesMany, tx, unspents, activationHeight,
|
2026-05-14 08:12:58 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
|
|
|
|
|
unspents[0] = plainPrevout(activationHeight);
|
|
|
|
|
auto genesisNft = tokenSpendTx({COutPoint(category, 0)},
|
2026-05-14 12:07:30 +02:00
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentB)});
|
|
|
|
|
tx = Tx::fromOldTransaction(genesisNft);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(genesisNft, tx, unspents, activationHeight,
|
2026-05-14 08:12:58 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 08:52:55 +02:00
|
|
|
void TransactionTests::may2023_cashtoken_nft_accounting_edge_cases()
|
|
|
|
|
{
|
|
|
|
|
ValidationFlags hf2023Flags;
|
|
|
|
|
hf2023Flags.hf201811Active = true;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
|
|
|
|
const int activationHeight = Params().GetConsensus().hf202305Height;
|
|
|
|
|
const uint256 category = uint256S("0x0000000000000000000000000000000000000000000000000000000000001234");
|
|
|
|
|
const std::vector<uint8_t> commitmentA = {0xaa};
|
|
|
|
|
const std::vector<uint8_t> commitmentB = {0xbb};
|
|
|
|
|
int64_t fees = 0;
|
|
|
|
|
uint32_t sigChecks = 0;
|
|
|
|
|
bool spendsCoinbase = false;
|
|
|
|
|
|
|
|
|
|
std::vector<UnspentOutputData> unspents;
|
|
|
|
|
unspents.push_back(tokenPrevout(tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton),
|
|
|
|
|
activationHeight));
|
|
|
|
|
auto mutableToMutable = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
2026-05-14 12:07:30 +02:00
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton)});
|
|
|
|
|
auto tx = Tx::fromOldTransaction(mutableToMutable);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(mutableToMutable, tx, unspents, activationHeight,
|
2026-05-14 08:52:55 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
|
2026-05-14 12:07:30 +02:00
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(category, Tx::HasNft | Tx::NftMintBaton), activationHeight);
|
2026-05-14 08:52:55 +02:00
|
|
|
auto burnMintingBaton = tokenSpendTx({COutPoint(uint256S("01"), 1)}, {CScript() << OP_TRUE});
|
2026-05-14 12:07:30 +02:00
|
|
|
tx = Tx::fromOldTransaction(burnMintingBaton);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(burnMintingBaton, tx, unspents, activationHeight,
|
2026-05-14 08:52:55 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
|
|
|
|
|
unspents[0] = tokenPrevout(tokenOutputScript(category, Tx::HasFtAmount, 10), activationHeight);
|
2026-05-14 12:07:30 +02:00
|
|
|
auto old = tokenSpendTx({COutPoint(uint256S("01"), 1)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentA)});
|
|
|
|
|
tx = Tx::fromOldTransaction(old);
|
|
|
|
|
expectTokenInputReject(old, tx, unspents, hf2023Flags, "bad-txns-token-nft-ex-nihilo");
|
2026-05-14 08:52:55 +02:00
|
|
|
|
|
|
|
|
unspents[0] = plainPrevout(activationHeight);
|
|
|
|
|
auto genesisCreatesMany = tokenSpendTx({COutPoint(category, 0)},
|
|
|
|
|
{tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment, 0, commitmentA),
|
|
|
|
|
tokenOutputScript(category, Tx::HasNft | Tx::NftMutableBaton),
|
|
|
|
|
tokenOutputScript(category, Tx::HasNft | Tx::NftMintBaton),
|
|
|
|
|
tokenOutputScript(category, Tx::HasNft | Tx::HasCommitment | Tx::HasFtAmount,
|
|
|
|
|
25, commitmentB)});
|
2026-05-14 12:07:30 +02:00
|
|
|
tx = Tx::fromOldTransaction(genesisCreatesMany);
|
|
|
|
|
ValidationPrivate::validateTransactionInputs(genesisCreatesMany, tx, unspents, activationHeight,
|
2026-05-14 08:52:55 +02:00
|
|
|
hf2023Flags, fees, sigChecks, spendsCoinbase, false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 16:48:15 +02:00
|
|
|
void TransactionTests::may2023_contextual_transaction_rules()
|
|
|
|
|
{
|
2026-05-08 23:38:26 +02:00
|
|
|
/** The minimum allowed transaction size after the Nov 2018 upgrade and before May 2023 */
|
|
|
|
|
static constexpr unsigned int MIN_TX_SIZE_FROM2018 = 100;
|
|
|
|
|
/** The minimum allowed transaction size after the May 2023 upgrade */
|
|
|
|
|
static constexpr unsigned int MIN_TX_SIZE_FROM2023 = 65;
|
|
|
|
|
|
2026-05-08 16:48:15 +02:00
|
|
|
ValidationFlags noUpgrade;
|
2026-05-08 23:38:26 +02:00
|
|
|
ValidationFlags hf2018Flags;
|
|
|
|
|
hf2018Flags.hf201811Active = true;
|
|
|
|
|
ValidationFlags hf2023Flags = hf2018Flags;
|
|
|
|
|
hf2023Flags.hf202305Active = true;
|
2026-05-08 16:48:15 +02:00
|
|
|
|
2026-05-08 23:38:26 +02:00
|
|
|
const Tx tx64 = makeTransactionOfSize(MIN_TX_SIZE_FROM2023 - 1);
|
|
|
|
|
QCOMPARE(tx64.size(), 64);
|
|
|
|
|
const Tx tx65 = makeTransactionOfSize(MIN_TX_SIZE_FROM2023);
|
|
|
|
|
QCOMPARE(tx65.size(), 65);
|
|
|
|
|
const Tx tx100 = makeTransactionOfSize(MIN_TX_SIZE_FROM2018);
|
|
|
|
|
QCOMPARE(tx100.size(), 100);
|
2026-05-08 16:48:15 +02:00
|
|
|
|
2026-05-08 23:38:26 +02:00
|
|
|
const Tx version2 = makeTransactionOfSize(120, 2);
|
|
|
|
|
const Tx version8 = makeTransactionOfSize(120, 8);
|
2026-05-08 16:48:15 +02:00
|
|
|
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx64, noUpgrade);
|
2026-05-08 23:38:26 +02:00
|
|
|
ValidationPrivate::checkContextualTransaction(tx65, noUpgrade);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx100, noUpgrade);
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx64, hf2018Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), "bad-txns-undersize");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx65, hf2018Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), "bad-txns-undersize");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx100, hf2018Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(version2, hf2018Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(version8, hf2018Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QFAIL("no!");
|
|
|
|
|
}
|
2026-05-08 16:48:15 +02:00
|
|
|
|
2026-05-08 23:38:26 +02:00
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx64, hf2023Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), "bad-txns-undersize");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(version2, hf2023Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), "bad-txns-version");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(version8, hf2023Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QCOMPARE(std::string(e.what()), "bad-txns-version");
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx65, hf2018Flags);
|
|
|
|
|
ValidationPrivate::checkContextualTransaction(tx100, hf2018Flags);
|
|
|
|
|
} catch (const Validation::Exception &e) {
|
|
|
|
|
QFAIL("no!");
|
|
|
|
|
}
|
2026-05-08 16:48:15 +02:00
|
|
|
}
|