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>
|
2026-05-18 09:36:10 +02:00
|
|
|
#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);
|
|
|
|
|
{
|
2021-05-04 16:57:55 +02:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
{
|
2021-05-04 16:57:55 +02:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-18 09:36:10 +02:00
|
|
|
|
|
|
|
|
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
|
|
|
|
2026-05-18 09:36:10 +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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-30 10:27:51 +02:00
|
|
|
void DoubleSpendProofTest::testStupidUsage()
|
|
|
|
|
{
|
2022-07-06 22:12:33 +02:00
|
|
|
PrivateKey key;
|
2022-05-11 13:46:15 +02:00
|
|
|
key.makeNewKey();
|
2020-09-30 10:27:51 +02:00
|
|
|
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.
|
2021-05-04 16:57:55 +02:00
|
|
|
TransactionBuilder builder;
|
2020-09-30 10:27:51 +02:00
|
|
|
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);
|
2020-09-30 10:27:51 +02:00
|
|
|
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
|
|
|
|
2021-05-04 16:57: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);
|
2021-04-19 17:07:02 +02:00
|
|
|
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();
|
|
|
|
|
|
2021-05-04 16:57:55 +02:00
|
|
|
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);
|
2021-04-19 17:07:02 +02:00
|
|
|
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 {
|
2021-04-19 17:07:02 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|