ced4f98beb
We add a Token class to Tx, which adds an easy to use API when compared to the plain iterator. The main point of the API chosen is to make sure we don't do unneeded parsing or copying for tokens unless needed. Additionally refactor the ValidationPrivate::UnspentOutput class to inherit from the Tx::Output class in order to avoid lots of duplicated logic on handling the data.
257 lines
9.1 KiB
C++
257 lines
9.1 KiB
C++
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2019-2021 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include "DoubleSpendProofTest.h"
|
|
|
|
#include <DoubleSpendProof.h>
|
|
#include <DoubleSpendProofStorage.h>
|
|
#include <TransactionBuilder.h>
|
|
#include <keystore.h>
|
|
#include <amount.h>
|
|
|
|
#include <primitives/PrivateKey.h>
|
|
#include <BitcoinVersion.h>
|
|
#include <streaming/streams.h>
|
|
|
|
namespace {
|
|
void createDoubleSpend(const Tx &in, int outIndex, const PrivateKey &key, Tx &out1, Tx &out2)
|
|
{
|
|
auto out = in.output(outIndex);
|
|
assert(out.outputValue >= 0);
|
|
{
|
|
TransactionBuilder builder;
|
|
builder.appendInput(in.createHash(), outIndex);
|
|
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
|
|
builder.appendOutput(50 * COIN);
|
|
PrivateKey k;
|
|
k.makeNewKey();
|
|
builder.pushOutputPay2Address(k.getPubKey().getKeyId());
|
|
out1 = builder.createTransaction();
|
|
}
|
|
{
|
|
TransactionBuilder builder;
|
|
builder.appendInput(in.createHash(), outIndex);
|
|
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
|
|
builder.appendOutput(50 * COIN);
|
|
PrivateKey k;
|
|
k.makeNewKey();
|
|
builder.pushOutputPay2Address(k.getPubKey().getKeyId());
|
|
out2 = builder.createTransaction();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoubleSpendProofTest::basic()
|
|
{
|
|
PrivateKey key;
|
|
key.makeNewKey();
|
|
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
|
|
blocks.front().findTransactions();
|
|
const Tx coinbase = blocks.front().transactions().at(0);
|
|
|
|
Tx first, second;
|
|
createDoubleSpend(coinbase, 0, key, first, second);
|
|
|
|
DoubleSpendProof dsp = DoubleSpendProof::create(first, second);
|
|
QVERIFY(!dsp.isEmpty());
|
|
QCOMPARE(dsp.prevTxId(), coinbase.createHash());
|
|
QCOMPARE(dsp.prevOutIndex(), 0);
|
|
|
|
auto s1 = dsp.firstSpender();
|
|
QCOMPARE(s1.lockTime, (uint32_t) 0);
|
|
QCOMPARE(s1.txVersion, (uint32_t) 2);
|
|
QCOMPARE(s1.outSequence, (uint32_t) 0xFFFFFFFF);
|
|
QVERIFY(s1.pushData.size() == 1);
|
|
QVERIFY(s1.pushData.front().size() >= 70);
|
|
QCOMPARE(s1.pushData.front().back(), (uint8_t) 65);
|
|
QVERIFY(!s1.hashOutputs.IsNull());
|
|
QVERIFY(!s1.hashSequence.IsNull());
|
|
QVERIFY(!s1.hashPrevOutputs.IsNull());
|
|
|
|
auto s2 = dsp.secondSpender();
|
|
QCOMPARE(s2.lockTime, (uint32_t) 0);
|
|
QCOMPARE(s2.txVersion, (uint32_t) 2);
|
|
QCOMPARE(s2.outSequence, (uint32_t) 0xFFFFFFFF);
|
|
QVERIFY(s2.pushData.size() == 1);
|
|
QVERIFY(s2.pushData.front().size() >= 70);
|
|
QCOMPARE(s2.pushData.front().back(), (uint8_t) 65);
|
|
QVERIFY(!s2.hashOutputs.IsNull());
|
|
QVERIFY(!s2.hashSequence.IsNull());
|
|
QVERIFY(!s2.hashPrevOutputs.IsNull());
|
|
|
|
// Will fail on MissingTransaction because we didn't add anything to the mempool yet.
|
|
QCOMPARE(dsp.validate(*bv->mempool()), DoubleSpendProof::MissingTransaction);
|
|
|
|
// add one to the mempool.
|
|
bv->mempool()->insertTx(first);
|
|
QCOMPARE(dsp.validate(*bv->mempool()), DoubleSpendProof::Valid);
|
|
}
|
|
|
|
void DoubleSpendProofTest::mempool()
|
|
{
|
|
PrivateKey key;
|
|
key.makeNewKey();
|
|
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
|
|
blocks.front().findTransactions();
|
|
const Tx coinbase = blocks.front().transactions().at(0);
|
|
|
|
Tx first, second;
|
|
createDoubleSpend(coinbase, 0, key, first, second);
|
|
bv->mempool()->insertTx(first);
|
|
|
|
auto future = bv->addTransaction(second);
|
|
QVERIFY(future.valid());
|
|
QCOMPARE(future.get(), "258: txn-mempool-conflict"); // wait until finished
|
|
|
|
QVERIFY(bv->mempool()->doubleSpendProofStorage()->proof(1).isEmpty() == false);
|
|
|
|
std::list<CTransaction> res;
|
|
bv->mempool()->remove(first.createOldTransaction(), res, false);
|
|
|
|
// after removing out mempool entry, the proof also goes away
|
|
QVERIFY(bv->mempool()->doubleSpendProofStorage()->proof(1).isEmpty());
|
|
}
|
|
|
|
void DoubleSpendProofTest::proofOrder()
|
|
{
|
|
PrivateKey key;
|
|
key.makeNewKey();
|
|
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
|
|
blocks.front().findTransactions();
|
|
const Tx coinbase = blocks.front().transactions().at(0);
|
|
|
|
Tx first, second;
|
|
createDoubleSpend(coinbase, 0, key, first, second);
|
|
DoubleSpendProof dsp1 = DoubleSpendProof::create(first, second);
|
|
DoubleSpendProof dsp2 = DoubleSpendProof::create(second, first);
|
|
|
|
// now, however we process them, the result is the same.
|
|
QCOMPARE(dsp1.firstSpender().pushData.front(), dsp2.firstSpender().pushData.front());
|
|
QCOMPARE(dsp1.secondSpender().pushData.front(), dsp2.secondSpender().pushData.front());
|
|
}
|
|
|
|
void DoubleSpendProofTest::serialization()
|
|
{
|
|
PrivateKey key;
|
|
key.makeNewKey();
|
|
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
|
|
blocks.front().findTransactions();
|
|
const Tx coinbase = blocks.front().transactions().at(0);
|
|
|
|
Tx first, second;
|
|
createDoubleSpend(coinbase, 0, key, first, second);
|
|
DoubleSpendProof dsp1 = DoubleSpendProof::create(first, second);
|
|
|
|
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
|
|
stream << dsp1;
|
|
const std::vector<uint8_t> blob(stream.begin(), stream.end());
|
|
// logFatal() << blob.size();
|
|
|
|
DoubleSpendProof dsp2;
|
|
CDataStream restore(blob, SER_NETWORK, PROTOCOL_VERSION);
|
|
restore >> dsp2;
|
|
|
|
QCOMPARE(dsp1.createHash(), dsp2.createHash());
|
|
|
|
// check if the second one validates
|
|
bv->mempool()->insertTx(second);
|
|
QCOMPARE(dsp2.validate(*bv->mempool()), DoubleSpendProof::Valid);
|
|
|
|
Streaming::BufferPool pool;
|
|
pool.reserve(blob.size());
|
|
memcpy(pool.begin(), &blob[0], blob.size());
|
|
DoubleSpendProof dsp3 = DoubleSpendProof::load(pool.commit(blob.size()));
|
|
QCOMPARE(dsp1.createHash(), dsp3.createHash());
|
|
|
|
// check if the 3rd one validates
|
|
QCOMPARE(dsp3.validate(*bv->mempool()), DoubleSpendProof::Valid);
|
|
}
|
|
|
|
void DoubleSpendProofTest::testStupidUsage()
|
|
{
|
|
PrivateKey key;
|
|
key.makeNewKey();
|
|
auto blocks = bv->appendChain(5, key, MockBlockValidation::FullOutScript);
|
|
blocks[3].findTransactions();
|
|
auto tx = blocks[3].transactions().at(0);
|
|
blocks[4].findTransactions();
|
|
|
|
try {
|
|
auto tx2 = blocks[4].transactions().at(0);
|
|
// coinbases can't be double spent (since they don't spent an output).
|
|
DoubleSpendProof::create(tx, tx2);
|
|
QFAIL("Coinbases can't be used to create a DSP");
|
|
} catch (const std::runtime_error &e) { /* ok */ }
|
|
|
|
// new Tx that spends a coinbase.
|
|
TransactionBuilder builder;
|
|
builder.appendInput(tx.createHash(), 0);
|
|
auto out = tx.output(0);
|
|
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
|
|
builder.appendOutput(50 * COIN);
|
|
tx = builder.createTransaction();
|
|
|
|
try {
|
|
DoubleSpendProof::create(tx, tx);
|
|
QFAIL("Wrong type of input should throw");
|
|
} catch (const std::runtime_error &e) { /* ok */ }
|
|
}
|
|
|
|
void DoubleSpendProofTest::bigTx()
|
|
{
|
|
PrivateKey key;
|
|
key.makeNewKey();
|
|
std::vector<Block> blocks = bv->appendChain(702, key, MockBlockValidation::FullOutScript);
|
|
|
|
TransactionBuilder builder;
|
|
for (size_t i = 0; i < 300; ++i) {
|
|
auto block = blocks.at(i);
|
|
block.findTransactions();
|
|
QCOMPARE(block.transactions().size(), 1ul);
|
|
auto tx = block.transactions().at(0);
|
|
builder.appendInput(tx.createHash(), 0);
|
|
auto out = tx.output(0);
|
|
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
|
|
builder.appendOutput(12 * COIN);
|
|
}
|
|
builder.pushOutputPay2Address(key.getPubKey().getKeyId());
|
|
auto first = builder.createTransaction();
|
|
|
|
TransactionBuilder builder2;
|
|
for (size_t i = 599; i >= 300; --i) {
|
|
auto block = blocks.at(i);
|
|
block.findTransactions();
|
|
QCOMPARE(block.transactions().size(), 1ul);
|
|
auto tx = block.transactions().at(0);
|
|
builder2.appendInput(tx.createHash(), 0);
|
|
auto out = tx.output(0);
|
|
builder2.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
|
|
builder2.appendOutput(3 * COIN);
|
|
}
|
|
|
|
builder2.pushOutputPay2Address(key.getPubKey().getKeyId());
|
|
auto second = builder2.createTransaction();
|
|
|
|
QBENCHMARK {
|
|
try {
|
|
DoubleSpendProof::create(first, second);
|
|
QFAIL("they don't double spend each other");
|
|
} catch (const std::exception &e) {}
|
|
}
|
|
}
|