722 lines
27 KiB
C++
722 lines
27 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 "TestApi.h"
|
|
|
|
#include <FloweeServiceApplication.h>
|
|
#include <QNetworkReply>
|
|
#include <QMetaEnum>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QTimer>
|
|
#include <QBuffer>
|
|
|
|
TestApi::TestApi()
|
|
{
|
|
}
|
|
|
|
void TestApi::start(const QString &hostname, int port)
|
|
{
|
|
m_port = port;
|
|
m_hostname = hostname;
|
|
if (m_hostname.isEmpty())
|
|
m_hostname = QLatin1String("http://localhost");
|
|
else if (m_port == 443)
|
|
m_hostname = "https://" + m_hostname;
|
|
else
|
|
m_hostname = "http://" + m_hostname;
|
|
|
|
// The Qt network access manager will serialize these since they all go to the same host.
|
|
// We create all, but wait for one test to start (the timeout-timer) for the next.
|
|
|
|
m_finishedRequests = 0;
|
|
TestAddressDetails::startRequest(this, m_network, TestAddressDetails::GET);
|
|
}
|
|
|
|
QString TestApi::hostname() const
|
|
{
|
|
return m_hostname;
|
|
}
|
|
|
|
int TestApi::port() const
|
|
{
|
|
return m_port;
|
|
}
|
|
|
|
void TestApi::finishedRequest()
|
|
{
|
|
switch (m_finishedRequests++) {
|
|
case 0:
|
|
TestAddressDetails::startRequest(this, m_network, TestAddressDetails::POST);
|
|
break;
|
|
case 1:
|
|
TestAddressDetails2::startRequest(this, m_network);
|
|
break;
|
|
case 2:
|
|
TestAddressUTXO::startRequest(this, m_network);
|
|
break;
|
|
case 3:
|
|
TestAddressUTXOPost::startRequest(this, m_network);
|
|
break;
|
|
case 4:
|
|
TestTransactionDetails::startRequest(this, m_network);
|
|
break;
|
|
case 5:
|
|
TestTransactionDetailsPost::startRequest(this, m_network);
|
|
break;
|
|
case 6:
|
|
GetRawTransactionVerbose::startRequest(this, m_network);
|
|
break;
|
|
case 7:
|
|
GetRawTransaction::startRequest(this, m_network);
|
|
break;
|
|
case 8:
|
|
SendRawTransaction::startRequest(this, m_network);
|
|
break;
|
|
case 9:
|
|
SendRawTransaction::startRequest(this, m_network, SendRawTransaction::POST);
|
|
break;
|
|
default:
|
|
QCoreApplication::quit();
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
FloweeServiceApplication app(argc, argv);
|
|
app.setOrganizationName("flowee");
|
|
app.setOrganizationDomain("flowee.org");
|
|
app.setApplicationName("rest-service-tester");
|
|
|
|
QCommandLineParser parser;
|
|
parser.setApplicationDescription("REST service test-client");
|
|
parser.addHelpOption();
|
|
app.addClientOptions(parser);
|
|
parser.process(app.arguments());
|
|
|
|
app.setup();
|
|
|
|
logFatal() << "Starting";
|
|
TestApi tester;
|
|
auto server = app.serverAddressFromArguments(3200);
|
|
tester.start(QString::fromStdString(server.hostname), server.announcePort);
|
|
|
|
return app.exec();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
|
|
AbstractTestCall::AbstractTestCall(QNetworkReply *parent, CallType callType)
|
|
: QObject(parent),
|
|
m_reply(parent),
|
|
m_callType(callType)
|
|
{
|
|
connect (m_reply, SIGNAL(finished()), this, SLOT(finished()));
|
|
QTimer::singleShot(10000, this, SLOT(timeout()));
|
|
}
|
|
|
|
void AbstractTestCall::startContext(const QString &context)
|
|
{
|
|
m_context = context;
|
|
}
|
|
|
|
void AbstractTestCall::error(const QString &error) {
|
|
m_errors.append({m_context, error});
|
|
}
|
|
|
|
void AbstractTestCall::finished()
|
|
{
|
|
Q_ASSERT(metaObject()->className() != QString("AbstractTestCall"));
|
|
logCritical().nospace() << metaObject()->className() + 4 << " [" << m_reply->url().toString()
|
|
<< " :" << (m_callType == GET ? "GET": "POST") << "]";
|
|
for (auto x : m_reply->rawHeaderList()) {
|
|
logInfo().nospace() << " " << QString::fromLatin1(x) << ": " << QString::fromUtf8(m_reply->rawHeader(x));
|
|
}
|
|
if (m_reply->error() == QNetworkReply::NoError) {
|
|
QJsonDocument doc = QJsonDocument::fromJson(m_reply->readAll());
|
|
if (doc.isNull()) {
|
|
error(" document could not be parsed, is it JSON?");
|
|
} else try {
|
|
checkDocument(doc);
|
|
} catch (...) {
|
|
if (m_errors.isEmpty())
|
|
m_errors.append(Error{"Runner", "Crashed"});
|
|
}
|
|
|
|
if (m_errors.isEmpty()) {
|
|
logCritical() << " ✓ all Ok";
|
|
} else {
|
|
for (auto e : m_errors) {
|
|
if (e.context.isEmpty())
|
|
logFatal() << " ❎" << e.error;
|
|
else
|
|
logFatal() << " ❎" << e.context << "|" << e.error;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
logFatal() << " ❎" << m_reply->errorString();
|
|
}
|
|
|
|
deleteLater();
|
|
emit requestDone();
|
|
}
|
|
|
|
void AbstractTestCall::timeout()
|
|
{
|
|
logCritical() << m_reply->url().toString();
|
|
logCritical() << " ❎ Request never returned";
|
|
deleteLater();
|
|
emit requestDone();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
QByteArray TestAddressDetails::s_postData = {
|
|
"{"
|
|
"\"addresses\": ["
|
|
"\"qqdkd86mqx4uxhqk6mcq0n7wt353j6kk9u85lmd68g\","
|
|
"\"qrehqueqhw629p6e57994436w730t4rzasnly00ht0\""
|
|
"]"
|
|
"}"
|
|
};
|
|
|
|
void TestAddressDetails::startRequest(TestApi *parent, QNetworkAccessManager &manager, CallType type)
|
|
{
|
|
QNetworkReply *reply = nullptr;
|
|
QString base("%1:%2/v2/address/details");
|
|
base = base.arg(parent->hostname()).arg(parent->port());
|
|
|
|
if (type == GET) {
|
|
reply = manager.get(QNetworkRequest(base + "/qqdkd86mqx4uxhqk6mcq0n7wt353j6kk9u85lmd68g"));
|
|
} else {
|
|
QNetworkRequest request(base);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
|
|
reply = manager.post(request, s_postData);
|
|
}
|
|
auto o = new TestAddressDetails(reply, type);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void TestAddressDetails::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
QJsonObject answer1, answer2;
|
|
if (m_callType == POST) {
|
|
if (!doc.isArray()) {
|
|
error("Root should be an array");
|
|
return;
|
|
}
|
|
|
|
auto array = doc.array();
|
|
if (array.size() != 2)
|
|
error("Root does not have correct number of elements");
|
|
answer1 = array.at(0).toObject();
|
|
answer2 = array.at(1).toObject();
|
|
}
|
|
else {
|
|
if (doc.isArray())
|
|
error("Root should not be an array");
|
|
answer1 = doc.object();
|
|
}
|
|
check(answer1, "balance", 0);
|
|
check(answer1, "balanceSat", 0);
|
|
check(answer1, "totalReceived", 49);
|
|
check(answer1, "totalReceivedSat", (double)4900000000);
|
|
check(answer1, "totalSent", 49);
|
|
check(answer1, "totalSentSat", (double)4900000000);
|
|
check(answer1, "cashAddress", "bitcoincash:qqdkd86mqx4uxhqk6mcq0n7wt353j6kk9u85lmd68g");
|
|
check(answer1, "legacyAddress", "13VtBWqnSRphhZRvUUir8FVnPZMGPGwi46");
|
|
|
|
auto txs = checkArray(answer1, "transactions", 2);
|
|
check(txs, 0, "ac771c02c80f4d70f7733a436e06f5de8ecc9e9988e9e5baf727fb479804c99d");
|
|
check(txs, 1, "bec03d0a5384f776e3cd351e37613c0e7924f081081b4352a1fcd69e2f2e8819");
|
|
|
|
if (m_callType == POST) {
|
|
check(answer2, "balance", 0.00582434);
|
|
check(answer2, "balanceSat", (double) 582434);
|
|
check(answer2, "totalReceived", 2.13667684);
|
|
check(answer2, "totalReceivedSat", (double) 213667684);
|
|
check(answer2, "totalSent", 2.1308525);
|
|
check(answer2, "totalSentSat", (double) 213085250);
|
|
check(answer2, "cashAddress", "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0");
|
|
check(answer2, "legacyAddress", "1PCBukyYULnmraUpMy2hW1Y1ngEQTN8DtF");
|
|
|
|
auto txs2 = checkArray(answer2, "transactions", 14);
|
|
check(txs2, 0, "0037c0460178a223ca2b90a987244908fd38d471dcae76a60754b170f7c29b93");
|
|
check(txs2, 1, "dbc04814c34a66185e3aa53b246bb7ddacc03d74d4801834434efd513e55c203");
|
|
check(txs2, 6, "ceb0cab0e37b59caf3ca29e1a698d19ff47f2827dd09cb2f3b91b9100b1dad1c");
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
void TestAddressDetails2::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString blockHeight("%1:%2/v2/address/details/bitcoincash:qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
auto reply = manager.get(QNetworkRequest(blockHeight.arg(parent->hostname()).arg(parent->port())));
|
|
auto o = new TestAddressDetails2(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void TestAddressDetails2::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (doc.isArray())
|
|
error("Root should not be an array");
|
|
QJsonObject root = doc.object();
|
|
check(root, "balance", (double) 39.00000547);
|
|
check(root, "balanceSat", (double) 3900000547);
|
|
check(root, "totalReceived", 10044.00000547);
|
|
check(root, "totalReceivedSat", (double) 1004400000547);
|
|
check(root, "totalSent", 10005);
|
|
check(root, "totalSentSat", (double) 1000500000000);
|
|
check(root, "cashAddress", "bitcoincash:qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
check(root, "legacyAddress", "15Fx34MisMrqThpkmFdC6U2uGW6SRKVwh4");
|
|
|
|
auto txs = checkArray(root, "transactions", 241);
|
|
check(txs, 1, "221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac");
|
|
check(txs, 2, "f2d5540968fe76c7d4ae0f183e34e873ea4deea492fde56cff5b2cc7920942f0");
|
|
check(txs, 3, "70a00f731e6b3bf959834f429a548546487d3f71d247cc78a12a78df9d1eb7de");
|
|
check(txs, 4, "a0d643d1f64fadb3d4039fe8b78d5d1ff8f16705613aa7918551abde57315af7");
|
|
check(txs, 5, "19c86fcdcb5f7c572f4d5d1176a2e00004ac9311f219ce469136f65fcf2985cf");
|
|
check(txs, 8, "db6d13b57fb0daef6ebb8af735a4b2776f11143e760d0c90e4251613bb00e43b");
|
|
check(txs, 226, "cbc4418fb87fde759fb02435b620774c0eccd1b238eab15a4c839a77a7c0cc0e");
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
void TestAddressUTXO::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString blockHeight("%1:%2/v2/address/utxo/qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
auto reply = manager.get(QNetworkRequest(blockHeight.arg(parent->hostname()).arg(parent->port())));
|
|
auto o = new TestAddressUTXO(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void TestAddressUTXO::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (doc.isArray())
|
|
error("Root should not be an array");
|
|
QJsonObject root = doc.object();
|
|
check(root, "cashAddress", "bitcoincash:qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
check(root, "legacyAddress", "15Fx34MisMrqThpkmFdC6U2uGW6SRKVwh4");
|
|
// TODO slpAddress ??
|
|
// scriptPubKey
|
|
// asm
|
|
|
|
auto utxos = checkArray(root, "utxos", 2);
|
|
auto tx0 = utxos[0];
|
|
check(tx0, "vout", 0);
|
|
check(tx0, "amount", 39);
|
|
check(tx0, "satoshis", (double) 3900000000);
|
|
check(tx0, "height", 178290);
|
|
check(tx0, "txid", "221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac");
|
|
// optional: confirmations
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
QByteArray TestAddressUTXOPost::s_postData = {
|
|
"{"
|
|
"\"addresses\": ["
|
|
"\"qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph\","
|
|
"\"qrehqueqhw629p6e57994436w730t4rzasnly00ht0\""
|
|
"]"
|
|
"}"
|
|
};
|
|
|
|
void TestAddressUTXOPost::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString base("%1:%2/v2/address/utxo");
|
|
QNetworkRequest request(base.arg(parent->hostname()).arg(parent->port()));
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
|
|
auto reply = manager.post(request, s_postData);
|
|
auto o = new TestAddressUTXOPost(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void TestAddressUTXOPost::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (!doc.isArray()) {
|
|
error("Root should be an array");
|
|
return;
|
|
}
|
|
QJsonArray array = doc.array();
|
|
if (array.size() != 2)
|
|
error("Incorrect number of root elements");
|
|
if (!array.at(0).isObject())
|
|
error("Array(0) should be an object");
|
|
QJsonObject root = array.at(0).toObject();
|
|
check(root, "cashAddress", "bitcoincash:qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
check(root, "legacyAddress", "15Fx34MisMrqThpkmFdC6U2uGW6SRKVwh4");
|
|
// TODO slpAddress ??
|
|
// scriptPubKey
|
|
// asm
|
|
|
|
auto utxos = checkArray(root, "utxos", 2);
|
|
auto tx0 = utxos[0];
|
|
check(tx0, "vout", 0);
|
|
check(tx0, "amount", 39);
|
|
check(tx0, "satoshis", (double) 3900000000);
|
|
check(tx0, "height", 178290);
|
|
check(tx0, "txid", "221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac");
|
|
// optional: confirmations
|
|
|
|
// TODO the second answer
|
|
root = array.at(1).toObject();
|
|
// TODO the answers
|
|
check(root, "cashAddress", "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0");
|
|
check(root, "legacyAddress", "1PCBukyYULnmraUpMy2hW1Y1ngEQTN8DtF");
|
|
// TODO slpAddress ??
|
|
// scriptPubKey
|
|
// asm
|
|
|
|
utxos = checkArray(root, "utxos", 2);
|
|
if (utxos.isEmpty())
|
|
return;
|
|
tx0 = utxos[0];
|
|
check(tx0, "vout", 0);
|
|
check(tx0, "amount", 0.00051061);
|
|
check(tx0, "satoshis", (double) 51061);
|
|
check(tx0, "height", 560615);
|
|
check(tx0, "txid", "b3792d28377b975560e1b6f09e48aeff8438d4c6969ca578bd406393bd50bd7d");
|
|
auto tx1 = utxos[1];
|
|
check(tx1, "vout", 1);
|
|
check(tx1, "amount", 0.00531373);
|
|
check(tx1, "satoshis", (double) 531373);
|
|
check(tx1, "height", 562106);
|
|
check(tx1, "txid", "1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb");
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
void TestTransactionDetails::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString blockHeight("%1:%2/v2/transaction/details/221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac");
|
|
auto reply = manager.get(QNetworkRequest(blockHeight.arg(parent->hostname()).arg(parent->port())));
|
|
auto o = new TestTransactionDetails(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void TestTransactionDetails::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (doc.isArray())
|
|
error("Root should not be an array");
|
|
checkDetails221fd0f3(doc.object());
|
|
checkDetails221fd0f3_more(doc.object());
|
|
}
|
|
|
|
void TestTransactionDetails::checkDetails221fd0f3(const QJsonObject &root)
|
|
{
|
|
check(root, "txid", "221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac");
|
|
check(root, "version", 1);
|
|
check(root, "locktime", 0);
|
|
check(root, "blockhash", "000000000000073e9769b8839e8b28f1d6a82eee6e3c94b3e866332bc0f86d13");
|
|
check(root, "blockheight", 178290);
|
|
check(root, "time", 1335978635);
|
|
check(root, "blocktime", 1335978635);
|
|
check(root, "size", 224);
|
|
|
|
auto inputs = checkArray(root, "vin", 1);
|
|
auto in1 = inputs[0];
|
|
check (in1, "txid", "d0519ef40c6704ccd8f55f0e14627f7d716d58df796ea4980875ab266daba6be");
|
|
check (in1, "vout", 1);
|
|
check (in1, "n", 0);
|
|
auto scriptSig = checkProp(in1, "scriptSig");
|
|
check (scriptSig, "hex", "4830450220588378deeafd55e05a2d5cc07fc7010990b"
|
|
"0738b0da32882e482e95df5c3b68a022100a36419800033620a7369423047a96cd"
|
|
"1e6537b54eb86f4f12a4d3c14819edad301410429042110774d8f75f01dceb2881"
|
|
"995ab34c46743f33859142991498adf93a27010446ab98b910a3924c3ea96a8d8b"
|
|
"1accf05a3fa54ebc2953ebf39f1d57890fd");
|
|
// check (scriptSig, "asm", "30450220588378deeafd55e05a2d5cc07fc7010990b07"
|
|
// "38b0da32882e482e95df5c3b68a022100a36419800033620a7369423047a96cd1e"
|
|
// "6537b54eb86f4f12a4d3c14819edad301 "
|
|
// "0429042110774d8f75f01dceb2881995ab34c46743f33859142991498adf93a270"
|
|
// "10446ab98b910a3924c3ea96a8d8b1accf05a3fa54ebc2953ebf39f1d57890fd");
|
|
|
|
auto outputs = checkArray(root, "vout", 1);
|
|
auto out1 = outputs[0];
|
|
check(out1, "n", 0);
|
|
|
|
auto scriptPubKey = checkProp(out1, "scriptPubKey");
|
|
check (scriptPubKey, "hex", "76a9142eb444957b51defb9908c51ddd1635961b2bd01f88ac");
|
|
// check (scriptPubKey, "asm", "OP_DUP OP_HASH160 2eb444957b51defb9908c51ddd16"
|
|
// "35961b2bd01f OP_EQUALVERIFY OP_CHECKSIG");
|
|
check (scriptPubKey, "type", "pubkeyhash");
|
|
}
|
|
|
|
void TestTransactionDetails::checkDetails221fd0f3_more(const QJsonObject &root)
|
|
{
|
|
check(root, "firstSeenTime", QJsonValue::Null);
|
|
check(root, "valueOut", 39);
|
|
check(root, "valueIn", 39);
|
|
check(root, "fees", QJsonValue::Null); // WFT? zero would be more appropriate
|
|
// optional: confirmations
|
|
|
|
auto inputs = checkArray(root, "vin", 1);
|
|
auto in1 = inputs[0];
|
|
check (in1, "value", (double)3900000000);
|
|
check (in1, "legacyAddress", "19rRh2VahedZdLxPhsJLjJWCwwEqRoS4PU");
|
|
check (in1, "cashAddress", "bitcoincash:qps3nla86vdczawucy28ha5reay2ghmwdc66x8xd85");
|
|
|
|
auto outputs = checkArray(root, "vout", 1);
|
|
auto out1 = outputs[0];
|
|
check(out1, "value", "39.00000000");
|
|
check(out1, "spentTxId", QJsonValue::Null);
|
|
check(out1, "spentIndex", QJsonValue::Null);
|
|
check(out1, "spentHeight", QJsonValue::Null);
|
|
|
|
auto scriptPubKey = checkProp(out1, "scriptPubKey");
|
|
auto ad1 = checkArray(scriptPubKey, "addresses", 1);
|
|
check(ad1, 0, "15Fx34MisMrqThpkmFdC6U2uGW6SRKVwh4");
|
|
auto ad2 = checkArray(scriptPubKey, "cashAddrs", 1);
|
|
check(ad2, 0, "bitcoincash:qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
QByteArray TestTransactionDetailsPost::s_postData = {
|
|
"{"
|
|
"\"txs\": ["
|
|
"\"221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac\","
|
|
"\"1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb\""
|
|
"]"
|
|
"}"
|
|
};
|
|
|
|
void TestTransactionDetailsPost::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString base("%1:%2/v2/transaction/details");
|
|
QNetworkRequest request(base.arg(parent->hostname()).arg(parent->port()));
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
auto reply = manager.post(request, s_postData);
|
|
auto o = new TestTransactionDetailsPost(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void TestTransactionDetailsPost::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (!doc.isArray())
|
|
error("Root should be an array");
|
|
auto array = doc.array();
|
|
if (array.size() != 2)
|
|
error("Expected 2 items on the array");
|
|
auto tx1 = array.at(0);
|
|
if (!tx1.isObject())
|
|
error("Item 0 should be an object {}");
|
|
checkDetails221fd0f3(tx1.toObject());
|
|
checkDetails221fd0f3_more(tx1.toObject());
|
|
|
|
auto tx2_ = array.at(1);
|
|
if (!tx2_.isObject())
|
|
error("Item 1 should be an object {}");
|
|
auto tx2 = tx2_.toObject();
|
|
check(tx2, "txid", "1afcc63b244182647909539ebe3f4a44b8ea4120a95edb8d9eebe5347b9491bb");
|
|
check(tx2, "version", 1);
|
|
check(tx2, "locktime", 0);
|
|
check(tx2, "blockhash", "0000000000000000045e5e52fb4f9746b3d15d3062855fd346aaef3debef4360");
|
|
check(tx2, "blockheight", 562106);
|
|
check(tx2, "time", 1545564654);
|
|
check(tx2, "blocktime", 1545564654);
|
|
check(tx2, "firstSeenTime", QJsonValue::Null);
|
|
check(tx2, "size", 437);
|
|
check(tx2, "valueOut", 0.47531373);
|
|
check(tx2, "valueIn", 0.47541373);
|
|
check(tx2, "fees", 0.0001);
|
|
// optional: confirmations
|
|
|
|
auto inputs = checkArray(tx2, "vin", 2);
|
|
auto in1 = inputs[0];
|
|
check (in1, "txid", "c42f8f16d3baa2ee343ea89ef110dfe094992379d08edd30887b8ca7ee671c9a");
|
|
check (in1, "vout", 0);
|
|
check (in1, "n", 0);
|
|
check (in1, "value", 25572607.);
|
|
check (in1, "legacyAddress", "1PCBukyYULnmraUpMy2hW1Y1ngEQTN8DtF");
|
|
check (in1, "cashAddress", "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0");
|
|
auto scriptSig = checkProp(in1, "scriptSig");
|
|
check (scriptSig, "hex", "4830450221008052d3b067418d53585fb8f91e1b57cf3"
|
|
"c040dc9c07a70f393ed663b3f7502c50220749aa8e09ac922e78cb474c8097873c"
|
|
"fb2634108d7acaa7db32a73a35743da974141044eb40b025df18409f2a5197b010"
|
|
"dd62a9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e"
|
|
"8b00b61b81b88a1b5ce6f24eac6b8a34b2c");
|
|
auto in2 = inputs[1];
|
|
check (in2, "txid", "e4a0ac48ff3f42fc342717a2a3d34248e5e85bae79d59bd20e1b60e61b1c500f");
|
|
check (in2, "vout", 1);
|
|
check (in2, "n", 1);
|
|
check (in2, "value", 21968766.);
|
|
check (in2, "legacyAddress", "1PCBukyYULnmraUpMy2hW1Y1ngEQTN8DtF");
|
|
check (in2, "cashAddress", "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0");
|
|
scriptSig = checkProp(in2, "scriptSig");
|
|
check (scriptSig, "hex", "473044022050d7fe7cdcec81eefa0987b88ddb83274d8e"
|
|
"9063d927090dc4c2d1db76c512d302207dc1eea439a627476265ed87f59cc9823fb"
|
|
"572ffc2640f0218d7bddc9a621c6e4141044eb40b025df18409f2a5197b010dd62a"
|
|
"9e65d9a74e415e5b10367721a9c4baa7ebfee22d14b8ece1c9bd70c0d9e5e8b00b6"
|
|
"1b81b88a1b5ce6f24eac6b8a34b2c");
|
|
|
|
auto outputs = checkArray(tx2, "vout", 2);
|
|
auto out1 = outputs[0];
|
|
check(out1, "value", "0.47000000");
|
|
check(out1, "n", 0);
|
|
check(out1, "spentTxId", "5994ec5d40d5c77d4cebd6988de5c4b58961539f3aca8f079ca39d923100adf6");
|
|
check(out1, "spentIndex", 0);
|
|
check(out1, "spentHeight", 626385);
|
|
|
|
auto scriptPubKey = checkProp(out1, "scriptPubKey");
|
|
check (scriptPubKey, "hex", "76a9147ab928d0b41194411a2e87a782b688c7cc69ba4688ac");
|
|
check (scriptPubKey, "type", "pubkeyhash");
|
|
auto ad1 = checkArray(scriptPubKey, "addresses", 1);
|
|
check(ad1, 0, "1CBuFWNQsRAy25xGsBoXTxNeRpd5t8be1a");
|
|
auto ad2 = checkArray(scriptPubKey, "cashAddrs", 1);
|
|
check(ad2, 0, "bitcoincash:qpatj2xsksgegsg696r60q4k3rruc6d6gc3srp333v");
|
|
|
|
|
|
auto out2 = outputs[1];
|
|
check(out2, "value", "0.00531373");
|
|
check(out2, "n", 1);
|
|
check(out2, "spentTxId", QJsonValue::Null);
|
|
check(out2, "spentIndex", QJsonValue::Null);
|
|
check(out2, "spentHeight", QJsonValue::Null);
|
|
|
|
scriptPubKey = checkProp(out2, "scriptPubKey");
|
|
check (scriptPubKey, "hex", "76a914f3707320bbb4a28759a78a5ad63a77a2f5d462ec88ac");
|
|
check (scriptPubKey, "type", "pubkeyhash");
|
|
ad1 = checkArray(scriptPubKey, "addresses", 1);
|
|
check(ad1, 0, "1PCBukyYULnmraUpMy2hW1Y1ngEQTN8DtF");
|
|
ad2 = checkArray(scriptPubKey, "cashAddrs", 1);
|
|
check(ad2, 0, "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0");
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
void GetRawTransactionVerbose::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString request("%1:%2/v2/rawtransactions/getRawTransaction/221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac?verbose=true");
|
|
auto reply = manager.get(QNetworkRequest(request.arg(parent->hostname()).arg(parent->port())));
|
|
auto o = new GetRawTransactionVerbose(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void GetRawTransactionVerbose::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (doc.isArray())
|
|
error("Root should not be an array");
|
|
auto root = doc.object();
|
|
checkDetails221fd0f3(root);
|
|
|
|
auto outputs = checkArray(root, "vout", 1);
|
|
auto out1 = outputs[0];
|
|
check(out1, "value", 39);
|
|
auto scriptPubKey = checkProp(out1, "scriptPubKey");
|
|
auto ad = checkArray(scriptPubKey, "addresses", 1);
|
|
check(ad, 0, "bitcoincash:qqhtg3y40dgaa7ueprz3mhgkxktpk27sru8t3l2zph");
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
void GetRawTransaction::startRequest(TestApi *parent, QNetworkAccessManager &manager)
|
|
{
|
|
QString request("%1:%2/v2/rawtransactions/getRawTransaction/221fd0f3b12d6d76027f21753fd64c644dbbf34405333ca1565a6a75d937c8ac");
|
|
auto reply = manager.get(QNetworkRequest(request.arg(parent->hostname()).arg(parent->port())));
|
|
auto o = new GetRawTransaction(reply);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
GetRawTransaction::GetRawTransaction(QNetworkReply *parent)
|
|
: m_reply(parent)
|
|
{
|
|
connect (m_reply, SIGNAL(finished()), this, SLOT(finished()));
|
|
QTimer::singleShot(10000, this, SLOT(timeout()));
|
|
}
|
|
|
|
void GetRawTransaction::finished()
|
|
{
|
|
QByteArray data = m_reply->readAll();
|
|
QString out = QString::fromLatin1(data);
|
|
|
|
if (out != "0100000001bea6ab6d26ab750898a46e79df586d717d7f621"
|
|
"40e5ff5d8cc04670cf49e51d0010000008b4830450220588378d"
|
|
"eeafd55e05a2d5cc07fc7010990b0738b0da32882e482e95df5c"
|
|
"3b68a022100a36419800033620a7369423047a96cd1e6537b54e"
|
|
"b86f4f12a4d3c14819edad301410429042110774d8f75f01dceb"
|
|
"2881995ab34c46743f33859142991498adf93a27010446ab98b9"
|
|
"10a3924c3ea96a8d8b1accf05a3fa54ebc2953ebf39f1d57890f"
|
|
"dffffffff01004775e8000000001976a9142eb444957b51defb9"
|
|
"908c51ddd1635961b2bd01f88ac00000000") {
|
|
|
|
logFatal() << " ❎" << "GetRawTransaction got the wrong hash back";
|
|
}
|
|
|
|
deleteLater();
|
|
emit requestDone();
|
|
}
|
|
|
|
void GetRawTransaction::timeout()
|
|
{
|
|
logCritical() << m_reply->url().toString();
|
|
logCritical() << " ❎ Request never returned";
|
|
deleteLater();
|
|
emit requestDone();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
|
|
static const QByteArray s_txToSend("01000000013ba3edfd7a7b12b27ac72c3e67768"
|
|
"f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76"
|
|
"f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0ecc"
|
|
"ce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941dd"
|
|
"c91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001"
|
|
"976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000");
|
|
|
|
QByteArray SendRawTransaction::s_postData("{"
|
|
"\"hexes\": [\"" + s_txToSend + "\"]}");
|
|
|
|
void SendRawTransaction::startRequest(TestApi *parent, QNetworkAccessManager &manager, CallType type)
|
|
{
|
|
QNetworkReply *reply = nullptr;
|
|
QString base("%1:%2/v2/rawtransactions/sendRawTransaction");
|
|
base = base.arg(parent->hostname()).arg(parent->port());
|
|
if (type == GET) {
|
|
reply = manager.get(QNetworkRequest(base + "/" + QString::fromLatin1(s_txToSend)));
|
|
}
|
|
else {
|
|
QNetworkRequest request(base);
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
reply = manager.post(request, s_postData);
|
|
}
|
|
auto o = new SendRawTransaction(reply, type);
|
|
connect (o, SIGNAL(requestDone()), parent, SLOT(finishedRequest()));
|
|
}
|
|
|
|
void SendRawTransaction::checkDocument(const QJsonDocument &doc)
|
|
{
|
|
if (doc.isArray())
|
|
error("Root should not be an array");
|
|
auto root = doc.object();
|
|
check(root, "error", "Missing inputs");
|
|
}
|