2019-07-24 13:35:31 +02:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
2025-02-12 22:16:17 +01:00
|
|
|
* Copyright (C) 2019-2025 Tom Zander <tom@flowee.org>
|
2019-07-24 13:35:31 +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 "TestAddressMonitor.h"
|
2021-03-17 21:07:25 +01:00
|
|
|
#include "TestData.h"
|
2019-07-24 13:35:31 +02:00
|
|
|
|
|
|
|
|
#include <streaming/BufferPool.h>
|
|
|
|
|
#include <streaming/MessageBuilder.h>
|
|
|
|
|
#include <streaming/MessageParser.h>
|
|
|
|
|
|
2019-09-02 23:35:43 +02:00
|
|
|
#include <DoubleSpendProof.h>
|
2019-07-24 13:35:31 +02:00
|
|
|
#include <uint256.h>
|
2019-10-20 20:00:57 +02:00
|
|
|
#include <cashaddr.h>
|
2019-07-24 13:35:31 +02:00
|
|
|
|
2021-11-02 11:04:59 +01:00
|
|
|
#include <primitives/Tx.h>
|
2019-09-02 23:35:43 +02:00
|
|
|
|
2019-07-24 13:35:31 +02:00
|
|
|
void TestAddressMonitor::testBasic()
|
|
|
|
|
{
|
|
|
|
|
startHubs(1);
|
|
|
|
|
|
2023-12-21 15:13:56 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
pool->reserve(50);
|
2019-07-24 13:35:31 +02:00
|
|
|
Streaming::MessageBuilder builder(pool);
|
2019-10-20 20:00:57 +02:00
|
|
|
|
|
|
|
|
builder.add(Api::AddressMonitor::BitcoinScriptHashed, uint256S("7cbd398b58e489e13100f2f7b0d56f5abc83a2381f9a841434a12447cc7a3b14"));
|
|
|
|
|
builder.add(Api::AddressMonitor::BitcoinScriptHashed, uint256S("00a7a0e144e7050ef5622b098faf19026631401fa46e68a93fe5e5630b94dcea"));
|
2019-07-24 13:35:31 +02:00
|
|
|
auto m = waitForReply(0, builder.message(Api::AddressMonitorService,
|
|
|
|
|
Api::AddressMonitor::Subscribe), Api::AddressMonitor::SubscribeReply);
|
|
|
|
|
QVERIFY(m.messageId() == Api::AddressMonitor::SubscribeReply);
|
|
|
|
|
|
|
|
|
|
feedDefaultBlocksToHub(0);
|
|
|
|
|
|
|
|
|
|
int total = 0;
|
|
|
|
|
for (auto message : m_hubs[0].messages) {
|
|
|
|
|
if (message.serviceId() == Api::AddressMonitorService) {
|
|
|
|
|
QVERIFY(message.messageId() == Api::AddressMonitor::SubscribeReply
|
|
|
|
|
|| message.messageId() == Api::AddressMonitor::TransactionFound);
|
|
|
|
|
if (message.messageId() != Api::AddressMonitor::TransactionFound)
|
|
|
|
|
continue;
|
|
|
|
|
++total;
|
|
|
|
|
bool seenAmount = false, seenOffsetInBlock = false, seenBlockHeight = false;
|
|
|
|
|
int seenAddress = 0;
|
|
|
|
|
Streaming::MessageParser p(message.body());
|
|
|
|
|
QCOMPARE(p.next(), Streaming::FoundTag);
|
|
|
|
|
while (true) {
|
2019-10-20 20:00:57 +02:00
|
|
|
if (p.tag() == Api::AddressMonitor::BitcoinScriptHashed) {
|
2019-07-24 13:35:31 +02:00
|
|
|
seenAddress++;
|
|
|
|
|
QCOMPARE(p.isByteArray(), true);
|
2019-10-20 20:00:57 +02:00
|
|
|
QCOMPARE(p.dataLength(), 32);
|
2019-07-24 13:35:31 +02:00
|
|
|
}
|
|
|
|
|
else if (p.tag() == Api::AddressMonitor::Amount) {
|
|
|
|
|
seenAmount = true;
|
|
|
|
|
QCOMPARE(p.isLong(), true);
|
|
|
|
|
QVERIFY(p.longData() > 0);
|
|
|
|
|
}
|
|
|
|
|
else if (p.tag() == Api::AddressMonitor::OffsetInBlock) {
|
|
|
|
|
seenOffsetInBlock = true;
|
|
|
|
|
QCOMPARE(p.isInt(), true);
|
|
|
|
|
QVERIFY(p.intData() > 80);
|
|
|
|
|
}
|
|
|
|
|
else if (p.tag() == Api::AddressMonitor::BlockHeight) {
|
|
|
|
|
seenBlockHeight = true;
|
|
|
|
|
QCOMPARE(p.isInt(), true);
|
|
|
|
|
QVERIFY(p.intData() > 0);
|
|
|
|
|
}
|
|
|
|
|
auto type = p.next();
|
|
|
|
|
QVERIFY(type != Streaming::Error);
|
|
|
|
|
if (type != Streaming::FoundTag)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVERIFY(seenAddress > 0);
|
|
|
|
|
QVERIFY(seenAddress <= 2);
|
|
|
|
|
QVERIFY(seenAmount);
|
|
|
|
|
QVERIFY(seenOffsetInBlock);
|
|
|
|
|
QVERIFY(seenBlockHeight);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-03 16:50:22 +02:00
|
|
|
QCOMPARE(total, 196);
|
2019-07-24 13:35:31 +02:00
|
|
|
}
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
void TestAddressMonitor::testDoubleSpendProof()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* In this test we use the existing testing chain and spend one of the outputs twice.
|
|
|
|
|
* Both transactions have 1 in, 1 out.
|
|
|
|
|
* The outputs are different and the addresses are the ones hardcoded in the onConnected()
|
|
|
|
|
* method so the monitor should always trigger on our sending those tx's to the nodes.
|
|
|
|
|
*
|
|
|
|
|
* We first send one tx to one node, then (after waiting for propagation) the other to
|
|
|
|
|
* the second node.
|
|
|
|
|
* Then we wait for the double spend proofs to be send to us from both nodes.
|
|
|
|
|
* The last node that actually saw the double spend just parrots it to us, with a transaction
|
|
|
|
|
* and not a proof.
|
|
|
|
|
* Then the other node (the first) should get a double spend as proof over p2p which we should
|
|
|
|
|
* get from the monitor.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// on connect, always subscribe to the two outputs that
|
|
|
|
|
struct MonitorAddressesInit {
|
|
|
|
|
explicit MonitorAddressesInit(NetworkManager *nm) : network(nm) { Q_ASSERT(nm); }
|
|
|
|
|
void onConnected(const EndPoint &ep) {
|
2023-12-21 15:13:56 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
pool->reserve(50);
|
2019-09-02 23:35:43 +02:00
|
|
|
Streaming::MessageBuilder builder(pool);
|
2019-10-20 20:00:57 +02:00
|
|
|
// this is a hash of the full script of a p2pkh output script (see CashAddress::createHashedOutputScript())
|
|
|
|
|
builder.add(Api::AddressMonitor::BitcoinScriptHashed, uint256S("7c3cb6eb855660b775bbe66e1c245beb405000cc1c5374771a474051685b6e33"));
|
|
|
|
|
builder.add(Api::AddressMonitor::BitcoinScriptHashed, uint256S("f324a872150702b3ba647c5fc39a5c8d36519b2d1430109321a89112102f3ec8"));
|
2019-09-02 23:35:43 +02:00
|
|
|
auto subscribeMessage = builder.message(Api::AddressMonitorService, Api::AddressMonitor::Subscribe);
|
|
|
|
|
network->connection(ep).send(subscribeMessage);
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
NetworkManager *network;
|
|
|
|
|
};
|
|
|
|
|
|
2022-08-20 19:18:34 +02:00
|
|
|
auto *subscriber = new MonitorAddressesInit (&m_network);
|
|
|
|
|
// one for each hub we will start
|
|
|
|
|
m_onConnectCallbacks.push_back(std::bind(&MonitorAddressesInit::onConnected, subscriber, std::placeholders::_1));
|
|
|
|
|
m_onConnectCallbacks.push_back(std::bind(&MonitorAddressesInit::onConnected, subscriber, std::placeholders::_1));
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
startHubs(2);
|
|
|
|
|
feedDefaultBlocksToHub(0);
|
2020-03-19 14:59:31 +01:00
|
|
|
QVERIFY(waitForHeight(115)); // make sure all nodes are at the same tip.
|
2019-09-02 23:35:43 +02:00
|
|
|
|
2023-12-21 15:13:56 +01:00
|
|
|
auto pool = std::make_shared<Streaming::BufferPool>();
|
|
|
|
|
auto txs = TestData::createDoubleSpend(pool);
|
2021-03-17 21:07:25 +01:00
|
|
|
Tx tx1 = txs.first;
|
|
|
|
|
Tx tx2 = txs.second;
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
logDebug() << "Sending tx1 to hub0" << tx1.createHash();
|
|
|
|
|
|
|
|
|
|
// I sent one tx to peer zero, and wait for it to be synchronized on peer 1 as well.
|
|
|
|
|
m_hubs[1].m_waitForMessageId = Api::AddressMonitor::TransactionFound;
|
|
|
|
|
m_hubs[1].m_waitForServiceId = Api::AddressMonitorService;
|
|
|
|
|
m_hubs[1].m_waitForMessageId2 = -1;
|
2022-08-20 19:18:34 +02:00
|
|
|
m_hubs[1].m_foundMessage.storeRelaxed(nullptr);
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
Streaming::MessageBuilder builder(pool);
|
|
|
|
|
builder.add(Api::LiveTransactions::GenericByteData, tx1.data());
|
|
|
|
|
con[0].send(builder.message(Api::LiveTransactionService, Api::LiveTransactions::SendTransaction));
|
|
|
|
|
|
2022-08-20 19:18:34 +02:00
|
|
|
QTRY_VERIFY_WITH_TIMEOUT(m_hubs[1].m_foundMessage.loadRelaxed() != nullptr, 50000);
|
|
|
|
|
auto m = *m_hubs[1].m_foundMessage.loadRelaxed();
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(m.serviceId(), (int) Api::AddressMonitorService);
|
|
|
|
|
QCOMPARE(m.messageId(), (int) Api::AddressMonitor::TransactionFound);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// now we send the second tx and expect a double-spend-notification from both peers.
|
|
|
|
|
// This will be propagated by proof between them.
|
|
|
|
|
|
|
|
|
|
m_hubs[0].m_waitForMessageId = m_hubs[1].m_waitForMessageId = Api::AddressMonitor::DoubleSpendFound;
|
|
|
|
|
m_hubs[0].m_waitForServiceId = Api::AddressMonitorService;
|
|
|
|
|
m_hubs[0].m_waitForMessageId2 = -1;
|
2022-08-20 19:18:34 +02:00
|
|
|
m_hubs[0].m_foundMessage.storeRelaxed(nullptr);
|
|
|
|
|
m_hubs[1].m_foundMessage.storeRelaxed(nullptr);
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
logDebug() << "Sending tx2 to hub1" << tx2.createHash();
|
|
|
|
|
// double-spend-tx (same input, other output and amount)
|
2020-09-02 13:52:36 +02:00
|
|
|
Streaming::MessageBuilder builder2(pool);
|
|
|
|
|
builder2.add(Api::LiveTransactions::GenericByteData, tx2.data());
|
2019-09-02 23:35:43 +02:00
|
|
|
// Sent this to HUB 1
|
2020-09-02 13:52:36 +02:00
|
|
|
con[1].send(builder2.message(Api::LiveTransactionService, Api::LiveTransactions::SendTransaction));
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
|
2019-10-20 20:00:57 +02:00
|
|
|
// from hub 1 I should have received an old fashioned double spend. The one with TX.
|
2022-08-20 19:18:34 +02:00
|
|
|
QTRY_VERIFY(m_hubs[1].m_foundMessage.loadRelaxed() != nullptr);
|
|
|
|
|
m = *m_hubs[1].m_foundMessage.loadRelaxed();
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(m.serviceId(), (int) Api::AddressMonitorService);
|
|
|
|
|
QCOMPARE(m.messageId(), (int) Api::AddressMonitor::DoubleSpendFound);
|
|
|
|
|
|
|
|
|
|
Streaming::MessageParser p(m);
|
|
|
|
|
p.next();
|
2019-10-20 20:00:57 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::BitcoinScriptHashed);
|
|
|
|
|
QCOMPARE(p.dataLength(), 32);
|
2019-09-02 23:35:43 +02:00
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Amount);
|
2021-02-16 19:13:05 +01:00
|
|
|
// the amounts order isn't known, just make sure each appears once.
|
|
|
|
|
QSet<uint64_t> amounts = {12494842l ,12484842l };
|
|
|
|
|
auto f = amounts.find(p.longData());
|
|
|
|
|
QVERIFY(f != amounts.end());
|
|
|
|
|
amounts.erase(f);
|
2019-09-02 23:35:43 +02:00
|
|
|
p.next();
|
2025-02-12 22:16:17 +01:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_Index);
|
|
|
|
|
QCOMPARE(p.intData(), 0);
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_CT_IsFT);
|
|
|
|
|
QCOMPARE(p.boolData(), false);
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_CT_IsNFT);
|
|
|
|
|
QCOMPARE(p.boolData(), false);
|
|
|
|
|
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::BitcoinScriptHashed);
|
|
|
|
|
QCOMPARE(p.dataLength(), 32);
|
|
|
|
|
p.next();
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Amount);
|
2021-02-16 19:13:05 +01:00
|
|
|
f = amounts.find(p.longData());
|
|
|
|
|
QVERIFY(f != amounts.end());
|
2019-09-02 23:35:43 +02:00
|
|
|
p.next();
|
2025-02-12 22:16:17 +01:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_Index);
|
|
|
|
|
QCOMPARE(p.intData(), 0);
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_CT_IsFT);
|
|
|
|
|
QCOMPARE(p.boolData(), false);
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_CT_IsNFT);
|
|
|
|
|
QCOMPARE(p.boolData(), false);
|
|
|
|
|
p.next();
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::TxId);
|
|
|
|
|
QCOMPARE(p.dataLength(), 32);
|
|
|
|
|
p.next();
|
2020-05-19 17:39:21 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::TransactionData); // the transaction.
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(p.dataLength(), 192);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// From peer 0 I get a double spend notifaction as well, but one based on the proof.
|
2022-08-20 19:18:34 +02:00
|
|
|
QTRY_VERIFY(m_hubs[0].m_foundMessage.loadRelaxed() != nullptr);
|
|
|
|
|
m = *m_hubs[0].m_foundMessage.loadRelaxed();
|
2019-09-02 23:35:43 +02:00
|
|
|
|
|
|
|
|
p = Streaming::MessageParser(m);
|
|
|
|
|
p.next();
|
2019-10-20 20:00:57 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::BitcoinScriptHashed);
|
|
|
|
|
QCOMPARE(p.dataLength(), 32);
|
2019-09-02 23:35:43 +02:00
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Amount);
|
|
|
|
|
QCOMPARE(p.longData(), (uint64_t) 12494842);
|
|
|
|
|
p.next();
|
2025-02-12 22:16:17 +01:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_Index);
|
|
|
|
|
QCOMPARE(p.intData(), 0);
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_CT_IsFT);
|
|
|
|
|
QCOMPARE(p.boolData(), false);
|
|
|
|
|
p.next();
|
|
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::Tx_Out_CT_IsNFT);
|
|
|
|
|
QCOMPARE(p.boolData(), false);
|
|
|
|
|
p.next();
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::TxId);
|
|
|
|
|
QCOMPARE(p.dataLength(), 32);
|
|
|
|
|
p.next();
|
2020-05-19 17:39:21 +02:00
|
|
|
QCOMPARE(p.tag(), (uint32_t) Api::AddressMonitor::DoubleSpendProofData); // the proof
|
2019-09-02 23:35:43 +02:00
|
|
|
QCOMPARE(p.dataLength(), 400);
|
|
|
|
|
}
|
2021-03-19 14:18:40 +01:00
|
|
|
|
|
|
|
|
QTEST_MAIN(TestAddressMonitor)
|