05df3fc68d
Removed AreInputsStandard from CTransaction, made it a regular function in main. Moved CTransaction::GetOutputFor to CCoinsViewCache. Moved GetLegacySigOpCount and GetP2SHSigOpCount out of CTransaction into regular functions in main. Moved GetValueIn and HaveInputs from CTransaction into CCoinsViewCache. Moved AllowFree, ClientCheckInputs, CheckInputs, UpdateCoins, and CheckTransaction out of CTransaction and into main. Moved IsStandard and IsFinal out of CTransaction and put them in main as IsStandardTx and IsFinalTx. Moved GetValueOut out of CTransaction into main. Moved CTxIn, CTxOut, and CTransaction into core. Added minimum fee parameter to CTxOut::IsDust() temporarily until CTransaction is moved to core.h so that CTxOut needn't know about CTransaction.
275 lines
11 KiB
C++
275 lines
11 KiB
C++
#include <map>
|
|
#include <string>
|
|
#include <boost/test/unit_test.hpp>
|
|
#include "json/json_spirit_writer_template.h"
|
|
|
|
#include "main.h"
|
|
#include "wallet.h"
|
|
|
|
using namespace std;
|
|
using namespace json_spirit;
|
|
|
|
// In script_tests.cpp
|
|
extern Array read_json(const std::string& filename);
|
|
extern CScript ParseScript(string s);
|
|
|
|
BOOST_AUTO_TEST_SUITE(transaction_tests)
|
|
|
|
BOOST_AUTO_TEST_CASE(tx_valid)
|
|
{
|
|
// Read tests from test/data/tx_valid.json
|
|
// Format is an array of arrays
|
|
// Inner arrays are either [ "comment" ]
|
|
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH
|
|
// ... where all scripts are stringified scripts.
|
|
Array tests = read_json("tx_valid.json");
|
|
|
|
BOOST_FOREACH(Value& tv, tests)
|
|
{
|
|
Array test = tv.get_array();
|
|
string strTest = write_string(tv, false);
|
|
if (test[0].type() == array_type)
|
|
{
|
|
if (test.size() != 3 || test[1].type() != str_type || test[2].type() != bool_type)
|
|
{
|
|
BOOST_ERROR("Bad test: " << strTest);
|
|
continue;
|
|
}
|
|
|
|
map<COutPoint, CScript> mapprevOutScriptPubKeys;
|
|
Array inputs = test[0].get_array();
|
|
bool fValid = true;
|
|
BOOST_FOREACH(Value& input, inputs)
|
|
{
|
|
if (input.type() != array_type)
|
|
{
|
|
fValid = false;
|
|
break;
|
|
}
|
|
Array vinput = input.get_array();
|
|
if (vinput.size() != 3)
|
|
{
|
|
fValid = false;
|
|
break;
|
|
}
|
|
|
|
mapprevOutScriptPubKeys[COutPoint(uint256(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str());
|
|
}
|
|
if (!fValid)
|
|
{
|
|
BOOST_ERROR("Bad test: " << strTest);
|
|
continue;
|
|
}
|
|
|
|
string transaction = test[1].get_str();
|
|
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
|
CTransaction tx;
|
|
stream >> tx;
|
|
|
|
CValidationState state;
|
|
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state), strTest);
|
|
BOOST_CHECK(state.IsValid());
|
|
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
|
{
|
|
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
|
|
{
|
|
BOOST_ERROR("Bad test: " << strTest);
|
|
break;
|
|
}
|
|
|
|
BOOST_CHECK_MESSAGE(VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], tx, i, test[2].get_bool() ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, 0), strTest);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(tx_invalid)
|
|
{
|
|
// Read tests from test/data/tx_invalid.json
|
|
// Format is an array of arrays
|
|
// Inner arrays are either [ "comment" ]
|
|
// or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, enforceP2SH
|
|
// ... where all scripts are stringified scripts.
|
|
Array tests = read_json("tx_invalid.json");
|
|
|
|
BOOST_FOREACH(Value& tv, tests)
|
|
{
|
|
Array test = tv.get_array();
|
|
string strTest = write_string(tv, false);
|
|
if (test[0].type() == array_type)
|
|
{
|
|
if (test.size() != 3 || test[1].type() != str_type || test[2].type() != bool_type)
|
|
{
|
|
BOOST_ERROR("Bad test: " << strTest);
|
|
continue;
|
|
}
|
|
|
|
map<COutPoint, CScript> mapprevOutScriptPubKeys;
|
|
Array inputs = test[0].get_array();
|
|
bool fValid = true;
|
|
BOOST_FOREACH(Value& input, inputs)
|
|
{
|
|
if (input.type() != array_type)
|
|
{
|
|
fValid = false;
|
|
break;
|
|
}
|
|
Array vinput = input.get_array();
|
|
if (vinput.size() != 3)
|
|
{
|
|
fValid = false;
|
|
break;
|
|
}
|
|
|
|
mapprevOutScriptPubKeys[COutPoint(uint256(vinput[0].get_str()), vinput[1].get_int())] = ParseScript(vinput[2].get_str());
|
|
}
|
|
if (!fValid)
|
|
{
|
|
BOOST_ERROR("Bad test: " << strTest);
|
|
continue;
|
|
}
|
|
|
|
string transaction = test[1].get_str();
|
|
CDataStream stream(ParseHex(transaction), SER_NETWORK, PROTOCOL_VERSION);
|
|
CTransaction tx;
|
|
stream >> tx;
|
|
|
|
CValidationState state;
|
|
fValid = CheckTransaction(tx, state) && state.IsValid();
|
|
|
|
for (unsigned int i = 0; i < tx.vin.size() && fValid; i++)
|
|
{
|
|
if (!mapprevOutScriptPubKeys.count(tx.vin[i].prevout))
|
|
{
|
|
BOOST_ERROR("Bad test: " << strTest);
|
|
break;
|
|
}
|
|
|
|
fValid = VerifyScript(tx.vin[i].scriptSig, mapprevOutScriptPubKeys[tx.vin[i].prevout], tx, i, test[2].get_bool() ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, 0);
|
|
}
|
|
|
|
BOOST_CHECK_MESSAGE(!fValid, strTest);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(basic_transaction_tests)
|
|
{
|
|
// 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};
|
|
vector<unsigned char> vch(ch, ch + sizeof(ch) -1);
|
|
CDataStream stream(vch, SER_DISK, CLIENT_VERSION);
|
|
CTransaction tx;
|
|
stream >> tx;
|
|
CValidationState state;
|
|
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state) && state.IsValid(), "Simple deserialized transaction should be valid.");
|
|
|
|
// Check that duplicate txins fail
|
|
tx.vin.push_back(tx.vin[0]);
|
|
BOOST_CHECK_MESSAGE(!CheckTransaction(tx, state) || !state.IsValid(), "Transaction with duplicate txins should be invalid.");
|
|
}
|
|
|
|
//
|
|
// Helper: create two dummy transactions, each with
|
|
// 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.
|
|
//
|
|
static std::vector<CTransaction>
|
|
SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsView & coinsRet)
|
|
{
|
|
std::vector<CTransaction> dummyTransactions;
|
|
dummyTransactions.resize(2);
|
|
|
|
// Add some keys to the keystore:
|
|
CKey key[4];
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
key[i].MakeNewKey(i % 2);
|
|
keystoreRet.AddKey(key[i]);
|
|
}
|
|
|
|
// Create some dummy input transactions
|
|
dummyTransactions[0].vout.resize(2);
|
|
dummyTransactions[0].vout[0].nValue = 11*CENT;
|
|
dummyTransactions[0].vout[0].scriptPubKey << key[0].GetPubKey() << OP_CHECKSIG;
|
|
dummyTransactions[0].vout[1].nValue = 50*CENT;
|
|
dummyTransactions[0].vout[1].scriptPubKey << key[1].GetPubKey() << OP_CHECKSIG;
|
|
coinsRet.SetCoins(dummyTransactions[0].GetHash(), CCoins(dummyTransactions[0], 0));
|
|
|
|
dummyTransactions[1].vout.resize(2);
|
|
dummyTransactions[1].vout[0].nValue = 21*CENT;
|
|
dummyTransactions[1].vout[0].scriptPubKey.SetDestination(key[2].GetPubKey().GetID());
|
|
dummyTransactions[1].vout[1].nValue = 22*CENT;
|
|
dummyTransactions[1].vout[1].scriptPubKey.SetDestination(key[3].GetPubKey().GetID());
|
|
coinsRet.SetCoins(dummyTransactions[1].GetHash(), CCoins(dummyTransactions[1], 0));
|
|
|
|
return dummyTransactions;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_Get)
|
|
{
|
|
CBasicKeyStore keystore;
|
|
CCoinsView coinsDummy;
|
|
CCoinsViewCache coins(coinsDummy);
|
|
std::vector<CTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
|
|
|
|
CTransaction t1;
|
|
t1.vin.resize(3);
|
|
t1.vin[0].prevout.hash = dummyTransactions[0].GetHash();
|
|
t1.vin[0].prevout.n = 1;
|
|
t1.vin[0].scriptSig << std::vector<unsigned char>(65, 0);
|
|
t1.vin[1].prevout.hash = dummyTransactions[1].GetHash();
|
|
t1.vin[1].prevout.n = 0;
|
|
t1.vin[1].scriptSig << std::vector<unsigned char>(65, 0) << std::vector<unsigned char>(33, 4);
|
|
t1.vin[2].prevout.hash = dummyTransactions[1].GetHash();
|
|
t1.vin[2].prevout.n = 1;
|
|
t1.vin[2].scriptSig << std::vector<unsigned char>(65, 0) << std::vector<unsigned char>(33, 4);
|
|
t1.vout.resize(2);
|
|
t1.vout[0].nValue = 90*CENT;
|
|
t1.vout[0].scriptPubKey << OP_1;
|
|
|
|
BOOST_CHECK(AreInputsStandard(t1, coins));
|
|
BOOST_CHECK_EQUAL(coins.GetValueIn(t1), (50+21+22)*CENT);
|
|
|
|
// Adding extra junk to the scriptSig should make it non-standard:
|
|
t1.vin[0].scriptSig << OP_11;
|
|
BOOST_CHECK(!AreInputsStandard(t1, coins));
|
|
|
|
// ... as should not having enough:
|
|
t1.vin[0].scriptSig = CScript();
|
|
BOOST_CHECK(!AreInputsStandard(t1, coins));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_IsStandard)
|
|
{
|
|
CBasicKeyStore keystore;
|
|
CCoinsView coinsDummy;
|
|
CCoinsViewCache coins(coinsDummy);
|
|
std::vector<CTransaction> dummyTransactions = SetupDummyInputs(keystore, coins);
|
|
|
|
CTransaction t;
|
|
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;
|
|
CKey key;
|
|
key.MakeNewKey(true);
|
|
t.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID());
|
|
|
|
BOOST_CHECK(IsStandardTx(t));
|
|
|
|
t.vout[0].nValue = 5011; // dust
|
|
BOOST_CHECK(!IsStandardTx(t));
|
|
|
|
t.vout[0].nValue = 6011; // not dust
|
|
BOOST_CHECK(IsStandardTx(t));
|
|
|
|
t.vout[0].scriptPubKey = CScript() << OP_1;
|
|
BOOST_CHECK(!IsStandardTx(t));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|