Files

314 lines
11 KiB
C++
Raw Permalink Normal View History

2019-09-02 23:35:43 +02:00
/*
* This file is part of the Flowee project
2021-01-20 21:35:20 +01:00
* Copyright (C) 2019-2021 Tom Zander <tom@flowee.org>
2019-09-02 23:35:43 +02: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/>.
*/
#include "DoubleSpendProofTest.h"
#include <DoubleSpendProof.h>
2019-09-04 14:11:53 +02:00
#include <DoubleSpendProofStorage.h>
2019-09-02 23:35:43 +02:00
#include <TransactionBuilder.h>
#include <keystore.h>
#include <amount.h>
2023-11-24 18:16:32 +01:00
#include <primitives/PrivateKey.h>
2022-07-06 22:50:53 +02:00
#include <BitcoinVersion.h>
#include <streaming/BufferPools.h>
#include <streaming/P2PBuilder.h>
#include <streaming/P2PParser.h>
2019-09-02 23:35:43 +02:00
#include <streaming/streams.h>
namespace {
2022-07-06 22:12:33 +02:00
void createDoubleSpend(const Tx &in, int outIndex, const PrivateKey &key, Tx &out1, Tx &out2)
2019-09-02 23:35:43 +02:00
{
auto out = in.output(outIndex);
assert(out.outputValue >= 0);
{
TransactionBuilder builder;
2026-05-05 00:39:16 +02:00
builder.appendInput(in.createHash(), outIndex);
2026-05-13 16:43:23 +02:00
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
2019-09-02 23:35:43 +02:00
builder.appendOutput(50 * COIN);
2022-07-06 22:12:33 +02:00
PrivateKey k;
2022-05-11 13:46:15 +02:00
k.makeNewKey();
builder.pushOutputPay2Address(k.getPubKey().getKeyId());
2019-09-02 23:35:43 +02:00
out1 = builder.createTransaction();
}
{
TransactionBuilder builder;
2026-05-05 00:39:16 +02:00
builder.appendInput(in.createHash(), outIndex);
2026-05-13 16:43:23 +02:00
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
2019-09-02 23:35:43 +02:00
builder.appendOutput(50 * COIN);
2022-07-06 22:12:33 +02:00
PrivateKey k;
2022-05-11 13:46:15 +02:00
k.makeNewKey();
builder.pushOutputPay2Address(k.getPubKey().getKeyId());
2019-09-02 23:35:43 +02:00
out2 = builder.createTransaction();
}
}
void writeProofPrefix(Streaming::P2PBuilder &builder)
{
uint256 hash;
builder.writeByteArray(hash, Streaming::RawBytes);
builder.writeInt(0);
builder.writeInt(2);
builder.writeInt(0xFFFFFFFF);
builder.writeInt(0);
builder.writeByteArray(hash, Streaming::RawBytes);
builder.writeByteArray(hash, Streaming::RawBytes);
builder.writeByteArray(hash, Streaming::RawBytes);
}
Streaming::ConstBuffer proofPrefixWithFirstPushDataHeader(uint64_t count, uint64_t pushDataSize)
{
auto pool = Streaming::pool(512);
Streaming::P2PBuilder builder(pool);
writeProofPrefix(builder);
builder.writeCompactSize(count);
if (count == 1)
builder.writeCompactSize(pushDataSize);
return builder.buffer();
}
CDataStream streamPrefixWithFirstPushDataHeader(uint64_t count, uint64_t pushDataSize)
{
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
uint256 hash;
stream << hash << int32_t(0);
stream << int32_t(2) << int32_t(0xFFFFFFFF) << int32_t(0);
stream << hash << hash << hash;
WriteCompactSize(stream, count);
if (count == 1)
WriteCompactSize(stream, pushDataSize);
return stream;
}
2019-09-02 23:35:43 +02:00
}
void DoubleSpendProofTest::basic()
{
2022-07-06 22:12:33 +02:00
PrivateKey key;
2022-05-11 13:46:15 +02:00
key.makeNewKey();
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
2019-09-02 23:35:43 +02:00
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);
2020-06-13 20:48:54 +02:00
QCOMPARE(s1.txVersion, (uint32_t) 2);
2019-09-02 23:35:43 +02:00
QCOMPARE(s1.outSequence, (uint32_t) 0xFFFFFFFF);
QVERIFY(s1.pushData.size() == 1);
2019-10-15 19:20:54 +02:00
QVERIFY(s1.pushData.front().size() >= 70);
2019-09-02 23:35:43 +02:00
QCOMPARE(s1.pushData.front().back(), (uint8_t) 65);
QVERIFY(!s1.hashOutputs.IsNull());
QVERIFY(!s1.hashSequence.IsNull());
QVERIFY(!s1.hashPrevOutputs.IsNull());
2020-09-29 17:01:52 +02:00
auto s2 = dsp.secondSpender();
2019-09-02 23:35:43 +02:00
QCOMPARE(s2.lockTime, (uint32_t) 0);
2020-06-13 20:48:54 +02:00
QCOMPARE(s2.txVersion, (uint32_t) 2);
2019-09-02 23:35:43 +02:00
QCOMPARE(s2.outSequence, (uint32_t) 0xFFFFFFFF);
QVERIFY(s2.pushData.size() == 1);
2019-10-15 19:20:54 +02:00
QVERIFY(s2.pushData.front().size() >= 70);
2019-09-02 23:35:43 +02:00
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.
2020-01-12 17:03:13 +01:00
QCOMPARE(dsp.validate(*bv->mempool()), DoubleSpendProof::MissingTransaction);
2019-09-02 23:35:43 +02:00
// add one to the mempool.
bv->mempool()->insertTx(first);
2020-01-08 16:22:14 +01:00
QCOMPARE(dsp.validate(*bv->mempool()), DoubleSpendProof::Valid);
2019-09-02 23:35:43 +02:00
}
2019-09-04 14:11:53 +02:00
void DoubleSpendProofTest::mempool()
{
2022-07-06 22:12:33 +02:00
PrivateKey key;
2022-05-11 13:46:15 +02:00
key.makeNewKey();
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
2019-09-04 14:11:53 +02:00
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());
}
2019-09-02 23:35:43 +02:00
void DoubleSpendProofTest::proofOrder()
{
2022-07-06 22:12:33 +02:00
PrivateKey key;
2022-05-11 13:46:15 +02:00
key.makeNewKey();
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
2019-09-02 23:35:43 +02:00
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());
2020-09-29 17:01:52 +02:00
QCOMPARE(dsp1.secondSpender().pushData.front(), dsp2.secondSpender().pushData.front());
2019-09-02 23:35:43 +02:00
}
void DoubleSpendProofTest::serialization()
{
2022-07-06 22:12:33 +02:00
PrivateKey key;
2022-05-11 13:46:15 +02:00
key.makeNewKey();
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(101, key, MockBlockValidation::FullOutScript);
2019-09-02 23:35:43 +02:00
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);
2020-01-08 16:22:14 +01:00
QCOMPARE(dsp2.validate(*bv->mempool()), DoubleSpendProof::Valid);
2021-01-20 21:35:20 +01:00
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);
2019-09-02 23:35:43 +02:00
}
2020-09-29 20:08:55 +02:00
void DoubleSpendProofTest::loadRejectsMalformedPushData()
{
auto tooManyItems = proofPrefixWithFirstPushDataHeader(2, 1);
QVERIFY_EXCEPTION_THROWN(DoubleSpendProof::load(tooManyItems), Streaming::ParsingException);
auto zeroBytes = proofPrefixWithFirstPushDataHeader(1, 0);
QVERIFY_EXCEPTION_THROWN(DoubleSpendProof::load(zeroBytes), Streaming::ParsingException);
auto oversized = proofPrefixWithFirstPushDataHeader(1, DoubleSpendProof::MaxPushDataSize + 1);
QVERIFY_EXCEPTION_THROWN(DoubleSpendProof::load(oversized), Streaming::ParsingException);
DoubleSpendProof proof;
auto stream = streamPrefixWithFirstPushDataHeader(1, DoubleSpendProof::MaxPushDataSize + 1);
QVERIFY_EXCEPTION_THROWN(stream >> proof, std::ios_base::failure);
}
void DoubleSpendProofTest::testStupidUsage()
{
2022-07-06 22:12:33 +02:00
PrivateKey key;
2022-05-11 13:46:15 +02:00
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);
2026-05-13 16:43:23 +02:00
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 */ }
}
2020-09-29 20:08:55 +02:00
void DoubleSpendProofTest::bigTx()
{
2022-07-06 22:12:33 +02:00
PrivateKey key;
2022-05-11 13:46:15 +02:00
key.makeNewKey();
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(702, key, MockBlockValidation::FullOutScript);
2020-09-29 20:08:55 +02:00
TransactionBuilder builder;
2020-09-29 20:08:55 +02:00
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);
2026-05-13 16:43:23 +02:00
builder.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
builder.appendOutput(12 * COIN);
2020-09-29 20:08:55 +02:00
}
2022-05-11 13:46:15 +02:00
builder.pushOutputPay2Address(key.getPubKey().getKeyId());
2020-09-29 20:08:55 +02:00
auto first = builder.createTransaction();
TransactionBuilder builder2;
2020-09-29 20:08:55 +02:00
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);
2026-05-13 16:43:23 +02:00
builder2.pushInputSignature(key, out.outputScript(), out.outputValue, TransactionBuilder::ECDSA);
builder2.appendOutput(3 * COIN);
2020-09-29 20:08:55 +02:00
}
2022-05-11 13:46:15 +02:00
builder2.pushOutputPay2Address(key.getPubKey().getKeyId());
2020-09-29 20:08:55 +02:00
auto second = builder2.createTransaction();
QBENCHMARK {
try {
DoubleSpendProof::create(first, second);
QFAIL("they don't double spend each other");
} catch (const std::exception &e) {}
2020-09-29 20:08:55 +02:00
}
}