Files

1191 lines
47 KiB
C++
Raw Permalink Normal View History

2019-11-09 19:08:38 +01:00
/*
* This file is part of the Flowee project
2021-02-11 15:52:00 +01:00
* Copyright (C) 2019-2021 Tom Zander <tom@flowee.org>
2019-11-09 19:08:38 +01: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/>.
*/
2019-11-30 19:46:30 +01:00
// #undef NDEBUG // make assert do something
2019-11-09 18:18:56 +01:00
#include "ContextData.h"
2021-02-14 19:44:28 +01:00
#include "Engine.h"
2019-11-28 23:58:56 +01:00
#include "Search.h"
2019-11-03 20:51:27 +01:00
#include <streaming/MessageBuilder.h>
#include <streaming/MessageParser.h>
#include <boost/algorithm/hex.hpp>
2019-11-08 23:05:03 +01:00
namespace {
2019-11-06 20:37:40 +01:00
2019-11-12 12:41:41 +01:00
// dummy 'getter' for write-only properties
Napi::Value returnValue(const Napi::CallbackInfo &)
{
return Napi::Value();
}
2019-11-08 23:05:03 +01:00
enum ApiOption {
Required,
NotRequired
};
2019-12-01 23:03:56 +01:00
bool arg(const Napi::CallbackInfo &info, size_t index, std::string &newValue, ApiOption required = NotRequired)
{
2019-11-08 23:05:03 +01:00
if (info.Length() > index) {
if (!info[index].IsString()) {
Napi::TypeError::New(info.Env(), "Wrong arguments").ThrowAsJavaScriptException();
return false;
}
newValue = info[index].ToString();
return true;
}
return required == NotRequired;
}
2019-11-09 18:18:56 +01:00
2019-12-01 23:03:56 +01:00
bool arg(const Napi::CallbackInfo &info, size_t index, int &newValue, ApiOption required = NotRequired)
{
2019-11-08 23:05:03 +01:00
if (info.Length() > index) {
if (!info[index].IsNumber()) {
Napi::TypeError::New(info.Env(), "Wrong arguments").ThrowAsJavaScriptException();
return false;
}
newValue = info[index].ToNumber().Int32Value();
return true;
}
return required == NotRequired;
2019-11-03 20:51:27 +01:00
}
2019-12-09 14:57:21 +01:00
Napi::Value setOnConnectedHub(const Napi::CallbackInfo &info)
{
assert(info.Length() == 1);
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
2021-02-17 21:02:06 +01:00
if (info[0].IsUndefined()) {
data->m_onHubConnect.reset();
}
else if (!info[0].IsFunction()) {
Napi::TypeError::New(info.Env(), "Expected function argument").ThrowAsJavaScriptException();
}
else {
data->m_onHubConnect.reset(new Flowee::Callback(Napi::ThreadSafeFunction::New(info.Env(),
info[0].As<Napi::Function>(),
"", 0 /* Unlimited queue */, 1 /* using threads */)));
}
2019-12-09 14:57:21 +01:00
return Napi::Value();
}
Napi::Value setOnConnectIndexer(const Napi::CallbackInfo &info)
{
assert(info.Length() == 1);
2021-02-17 21:02:06 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
if (info[0].IsUndefined()) {
data->m_onIndexerConnect.reset();
}
else if (!info[0].IsFunction()) {
2019-12-09 14:57:21 +01:00
Napi::TypeError::New(info.Env(), "Expected function argument").ThrowAsJavaScriptException();
return Napi::Value();
}
2021-02-17 21:02:06 +01:00
else {
data->m_onIndexerConnect.reset(new Flowee::Callback(Napi::ThreadSafeFunction::New(info.Env(),
info[0].As<Napi::Function>(),
"", 0 /* Unlimited queue */, 1 /* using threads */)));
}
2019-12-09 14:57:21 +01:00
return Napi::Value();
}
Napi::Value setOnConnected(const Napi::CallbackInfo &info)
{
assert(info.Length() == 1);
2021-02-17 21:02:06 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
if (info[0].IsUndefined()) {
data->m_onAllConnected.reset();
}
else if (!info[0].IsFunction()) {
2019-12-09 14:57:21 +01:00
Napi::TypeError::New(info.Env(), "Expected function argument").ThrowAsJavaScriptException();
return Napi::Value();
}
2021-02-17 21:02:06 +01:00
else {
data->m_onAllConnected.reset(new Flowee::Callback(Napi::ThreadSafeFunction::New(info.Env(),
info[0].As<Napi::Function>(),
"", 0 /* Unlimited queue */, 1 /* using threads */)));
}
2019-12-09 14:57:21 +01:00
return Napi::Value();
}
2019-12-30 17:13:29 +01:00
Napi::Value setOnAddressChanged(const Napi::CallbackInfo &info)
{
assert(info.Length() == 1);
2021-02-17 21:02:06 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
if (info[0].IsUndefined()) {
data->m_addressMonitorCallback.reset();
}
else if (!info[0].IsFunction()) {
2019-12-30 17:13:29 +01:00
Napi::TypeError::New(info.Env(), "Expected function argument").ThrowAsJavaScriptException();
return Napi::Value();
}
2021-02-17 21:02:06 +01:00
else {
data->m_addressMonitorCallback.reset(new Flowee::Callback(Napi::ThreadSafeFunction::New(info.Env(),
info[0].As<Napi::Function>(), "", 0 /* Unlimited queue */, 1 /* using threads */)));
}
2019-12-30 17:13:29 +01:00
return Napi::Value();
}
Napi::Value setOnChainChanged(const Napi::CallbackInfo &info)
{
assert(info.Length() == 1);
2021-02-17 21:02:06 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
if (info[0].IsUndefined()) {
data->m_newBlockCallback.reset();
data->engine()->setListenToBlockUpdates(false);
}
else if (!info[0].IsFunction()) {
Napi::TypeError::New(info.Env(), "Expected function argument").ThrowAsJavaScriptException();
return Napi::Value();
}
2021-02-17 21:02:06 +01:00
else {
data->m_newBlockCallback.reset(new Flowee::Callback(Napi::ThreadSafeFunction::New(info.Env(),
info[0].As<Napi::Function>(), "", 0 /* Unlimited queue */, 1 /* using threads */)));
data->engine()->setListenToBlockUpdates(true);
}
return Napi::Value();
}
2021-02-12 21:38:06 +01:00
Napi::Value setLogLevel(const Napi::CallbackInfo &info)
{
assert(info.Length() == 1);
if (!info[0].IsNumber()) {
Napi::TypeError::New(info.Env(), "Expected enum/number argument").ThrowAsJavaScriptException();
return Napi::Value();
}
Log::Verbosity v = static_cast<Log::Verbosity>(info[0].ToNumber().Int32Value());
if (v < Log::DebugLevel || v > Log::FatalLevel) {
Napi::TypeError::New(info.Env(), "logLevel value not understood").ThrowAsJavaScriptException();
return Napi::Value();
}
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
2021-02-14 19:44:28 +01:00
assert(data->engine());
data->engine()->setLogLevel(v);
2021-02-12 21:38:06 +01:00
return Napi::Value();
}
Napi::Value getLogLevel(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
2021-02-14 19:44:28 +01:00
assert(data->engine());
return Napi::Value::From(info.Env(), int(data->engine()->logLevel()));
2021-02-12 21:38:06 +01:00
}
2020-07-17 15:46:49 +02:00
Napi::Value contextData_sendMessage(const Napi::CallbackInfo &info)
2020-07-16 23:23:57 +02:00
{
2020-07-17 15:46:49 +02:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
return data->sendJsMessage(info);
2020-07-16 23:23:57 +02:00
}
2021-02-15 10:46:56 +01:00
Napi::Value contextData_shutdown(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
if (data)
2021-02-20 11:04:15 +01:00
data->shutdown(info.Env());
2021-02-15 10:46:56 +01:00
return Napi::Value();
}
2019-12-30 17:13:29 +01:00
2019-12-09 14:57:21 +01:00
// user called connect()
2019-11-09 19:08:38 +01:00
Napi::Value contextData_connect(const Napi::CallbackInfo &info)
2019-11-08 23:05:03 +01:00
{
2019-11-09 18:18:56 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
2019-11-08 23:05:03 +01:00
assert(data);
std::string hostname = "api.flowee.org";
if (!arg(info, 0, hostname))
2019-11-09 19:08:38 +01:00
return Napi::Value();
2019-11-08 23:05:03 +01:00
2021-02-14 19:44:28 +01:00
return data->connect(info.Env(), hostname, Engine::Mainnet);
2020-12-16 22:11:55 +01:00
}
// user called testConnect()
Napi::Value contextData_testConnect(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
std::string hostname = "api.flowee.org";
if (!arg(info, 0, hostname))
return Napi::Value();
2021-02-14 19:44:28 +01:00
return data->connect(info.Env(), hostname, Engine::Testnet4);
2019-11-08 23:05:03 +01:00
}
2019-12-09 14:57:21 +01:00
// user called connectHub()
2019-11-09 19:08:38 +01:00
Napi::Value contextData_connectHub(const Napi::CallbackInfo &info)
2019-11-08 23:05:03 +01:00
{
2019-11-09 18:18:56 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
2019-11-08 23:05:03 +01:00
assert(data);
std::string hostname = "api.flowee.org";
if (!arg(info, 0, hostname))
2019-11-09 19:08:38 +01:00
return Napi::Value();
2019-11-08 23:05:03 +01:00
int port = 1235;
2019-12-27 22:07:59 +01:00
if (!arg(info, 1, port))
2019-11-09 19:08:38 +01:00
return Napi::Value();
2019-12-09 14:57:21 +01:00
return data->connectHub(info.Env(), hostname, port);
2019-11-08 23:05:03 +01:00
}
2020-12-16 22:11:55 +01:00
// user called testConnectHub()
Napi::Value contextData_testConnectHub(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
std::string hostname = "api.flowee.org";
if (!arg(info, 0, hostname))
return Napi::Value();
int port = 21235;
if (!arg(info, 1, port))
return Napi::Value();
return data->connectHub(info.Env(), hostname, port);
}
2019-12-01 23:03:56 +01:00
struct HubVersion {
char *floweeVersion = nullptr;
2019-12-09 14:57:21 +01:00
ContextData *data = nullptr;
2019-12-01 23:03:56 +01:00
};
2019-12-09 14:57:21 +01:00
void contextData_hubConnectedCallback(Napi::Env env, Napi::Function jsCallback, HubVersion *hubVersion)
{
2019-12-09 14:57:21 +01:00
const Napi::String version = Napi::String::New(env, hubVersion->floweeVersion);
jsCallback.Call({ version });
hubVersion->data->m_hubConnectPromise.resolve(version);
2019-12-01 23:03:56 +01:00
2021-02-14 19:44:28 +01:00
if (hubVersion->data->engine()->isIndexerConnected())
2019-12-09 14:57:21 +01:00
hubVersion->data->m_fullConnectPromise.resolve(env, "ok");
delete hubVersion;
}
2019-12-01 23:03:56 +01:00
struct IndexerServices {
std::set<Blockchain::Service> services;
2019-12-09 14:57:21 +01:00
ContextData *data = nullptr;
2019-12-01 23:03:56 +01:00
};
void contextData_IndexerConnectedCallback(Napi::Env env, Napi::Function jsCallback, IndexerServices *is)
{
2019-12-01 23:03:56 +01:00
Napi::Array list = Napi::Array::New(env);
for (auto service : is->services) {
std::string name;
switch (service) {
case Blockchain::IndexerTxIdDb:
name = "txid-db";
break;
case Blockchain::IndexerAddressDb:
name = "address-db";
break;
case Blockchain::IndexerSpentDb:
name = "spent-db";
break;
default:
assert(false);
}
list[list.Length()] = Napi::String::New(env, name);
}
jsCallback.Call({ list });
2019-12-09 14:57:21 +01:00
is->data->m_indexerConnectPromise.resolve(list);
2021-02-14 19:44:28 +01:00
if (is->data->engine()->isHubConnected())
2019-12-09 14:57:21 +01:00
is->data->m_fullConnectPromise.resolve(env, "ok");
2019-12-01 23:03:56 +01:00
delete is;
}
2019-12-09 14:57:21 +01:00
void contextData_allConnectedCallback(Napi::Env env, Napi::Function jsCallback, void *)
{
jsCallback.Call({ Napi::String::New(env, "ok") });
}
2019-11-09 19:08:38 +01:00
Napi::Value contextData_connectIndexer(const Napi::CallbackInfo &info)
2019-11-08 23:05:03 +01:00
{
2019-11-09 18:18:56 +01:00
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
2019-11-08 23:05:03 +01:00
assert(data);
std::string hostname = "api.flowee.org";
if (!arg(info, 0, hostname))
2019-11-09 19:08:38 +01:00
return Napi::Value();
2019-11-08 23:05:03 +01:00
int port = 1234;
2019-12-27 22:07:59 +01:00
if (!arg(info, 1, port))
2019-11-09 19:08:38 +01:00
return Napi::Value();
2019-11-08 23:05:03 +01:00
if (port < 0 || port > 0xFFFF) {
Napi::TypeError::New(info.Env(), "Port out of range").ThrowAsJavaScriptException();
2019-11-09 19:08:38 +01:00
return Napi::Value();
2019-11-08 23:05:03 +01:00
}
2019-12-09 14:57:21 +01:00
return data->connectIndexer(info.Env(), hostname, port);
2019-11-08 23:05:03 +01:00
}
2020-12-16 22:11:55 +01:00
Napi::Value contextData_testConnectIndexer(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
std::string hostname = "api.flowee.org";
if (!arg(info, 0, hostname))
return Napi::Value();
int port = 21234;
if (!arg(info, 1, port))
return Napi::Value();
if (port < 0 || port > 0xFFFF) {
Napi::TypeError::New(info.Env(), "Port out of range").ThrowAsJavaScriptException();
return Napi::Value();
}
return data->connectIndexer(info.Env(), hostname, port);
}
2019-11-28 23:58:56 +01:00
Napi::Value contextData_startSearch(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
return data->startSearch(info);
}
Napi::Value contextData_fulfillFullConnectPromise(Napi::Env env, Napi::Function, ContextData *data)
2019-12-09 14:57:21 +01:00
{
data->m_fullConnectPromise.resolve(env, "ok");
2020-07-27 19:26:05 +02:00
data->m_hubConnectPromise.resolve(env, "ok");
data->m_indexerConnectPromise.resolve(env, "ok");
2019-12-09 14:57:21 +01:00
return Napi::Value();
}
Napi::Value contextData_sendTransaction(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
return data->sendTransaction(info);
}
2019-12-30 17:13:29 +01:00
Napi::Value contextData_subscribeToAddress(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
return data->updateAddressMonitor(info, ContextData::Subscribe);
}
Napi::Value contextData_unsubscribeAddress(const Napi::CallbackInfo &info)
{
ContextData *data = reinterpret_cast<ContextData*>(info.Data());
assert(data);
return data->updateAddressMonitor(info, ContextData::Unsubscribe);
}
2021-02-26 19:37:16 +01:00
Napi::Value contextData_parseAddress(const Napi::CallbackInfo &info)
{
if (info.Length() < 1 || info.Length() > 2) {
Napi::TypeError::New(info.Env(), "Wrong arguments count").ThrowAsJavaScriptException();
return Napi::Value();
}
if (!info[0].IsString()
|| (info.Length() == 2 && !info[1].IsString())) {
Napi::TypeError::New(info.Env(), "Expected string arguments").ThrowAsJavaScriptException();
return Napi::Value();
}
Streaming::ConstBuffer answer;
if (info.Length() == 2) {
// second argument is the expectedPrefix for decoding an address.
std::string prefix = info[1].ToString().Utf8Value();
answer = Flowee::parseAddress(info[0].ToString(), &prefix);
} else {
answer = Flowee::parseAddress(info[0].ToString());
}
return Flowee::wrap(info.Env(), answer);
}
struct NetMessageData {
Message message;
ContextData *data;
};
Napi::Value contextData_handleNetMessageFinished(Napi::Env env, Napi::Function, NetMessageData *data)
{
data->data->handleMessageForNetPromise(env, data->message);
delete data;
return Napi::Value();
}
2019-12-30 17:13:29 +01:00
Napi::Value contextData_handleAM(Napi::Env env, Napi::Function jsCallback, NetMessageData *data)
{
logDebug() << "AddressMonitor handling called";
Message message = data->message;
Streaming::MessageParser parser(message.body());
Napi::Object arg = Napi::Object::New(env);
std::string reason;
if (message.messageId() == Api::AddressMonitor::TransactionFound)
reason = "transaction-found";
else
reason = "double-spend";
2020-07-27 10:35:53 +02:00
Napi::Array bshs = Napi::Array::New(env);
Napi::Array addresses = Napi::Array::New(env);
2019-12-30 17:13:29 +01:00
while (parser.next() == Streaming::FoundTag) {
2020-07-27 10:35:53 +02:00
if (parser.tag() == Api::AddressMonitor::TxId) {
2019-12-30 17:13:29 +01:00
arg.Set("txid", Flowee::hashToString(env, parser.bytesDataBuffer()));
}
2020-07-27 10:35:53 +02:00
else if (parser.tag() == Api::AddressMonitor::BitcoinScriptHashed) {
Napi::Value bsh = Flowee::hashToString(env, parser.bytesDataBuffer());
bshs[bshs.Length()] = bsh;
2021-02-14 19:44:28 +01:00
Napi::Value address = Napi::String::From(env, data->data->engine()->addressForScriptHash(parser.bytesDataBuffer()));
2020-07-27 10:35:53 +02:00
addresses[addresses.Length()] = address;
if (bshs.Length() <= 1) {
arg.Set("address", address);
arg.Set("bitcoinScriptHashed", bsh);
}
}
else if (parser.tag() == Api::AddressMonitor::Amount) {
2019-12-30 17:13:29 +01:00
arg.Set("amount", Napi::Number::New(env, parser.longData()));
2020-07-27 10:35:53 +02:00
}
2019-12-30 17:13:29 +01:00
else if (parser.tag() == Api::AddressMonitor::OffsetInBlock) {
arg.Set("offsetInBlock", Napi::Number::New(env, parser.intData()));
reason = "transaction-mined";
}
2020-07-27 10:35:53 +02:00
else if (parser.tag() == Api::AddressMonitor::TransactionData) {
2019-12-30 17:13:29 +01:00
arg.Set("doubleSpentTx", Flowee::wrap(env, parser.bytesDataBuffer()));
2020-07-27 10:35:53 +02:00
}
else if (parser.tag() == Api::AddressMonitor::DoubleSpendProofData) {
arg.Set("doubleSpentProof", Flowee::wrap(env, parser.bytesDataBuffer()));
2020-07-27 10:35:53 +02:00
}
else if (parser.tag() == Api::AddressMonitor::BlockHeight) {
arg.Set("blockHeight", Napi::Number::New(env, parser.intData()));
}
2019-12-30 17:13:29 +01:00
}
arg.Set("reason", reason);
2020-07-27 10:35:53 +02:00
arg.Set("bitcoinScriptHashedArray", bshs);
arg.Set("addressArray", addresses);
2019-12-30 17:13:29 +01:00
delete data;
jsCallback.Call({ arg });
return Napi::Value();
}
Napi::Value contextData_handleChainChanged(Napi::Env env, Napi::Function jsCallback, NetMessageData *data)
{
Message message = data->message;
logDebug() << "chain-changed handling called" << message.messageId();
Streaming::MessageParser parser(message.body());
Napi::Object rc = Napi::Object::New(env);
Napi::Array array = Napi::Array::New(env);
Napi::Object arg = Napi::Object::New(env);
// we expect either of the two messages: Blocks Removed, or NewBlockOnChain
// when blocks were added.
// The 'removed' type lists each block-hash that got removed and as such
// we put it in an array.
const bool useArray = message.messageId() == Api::BlockNotification::BlocksRemoved;
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == Api::BlockNotification::BlockHeight) {
arg.Set("blockHeight", Napi::Number::New(env, parser.intData()));
if (useArray) {
array[array.Length()] = arg;
arg = Napi::Object::New(env);
}
} else if (parser.tag() == Api::BlockNotification::BlockHash) {
arg.Set("blockId", Flowee::hashToString(env, parser.bytesDataBuffer()));
arg.Set("blockHash", Flowee::wrap(env, parser.bytesDataBuffer()));
}
}
if (useArray) {
2021-02-19 19:55:51 +01:00
// Return array of removals
rc.Set("removed", array);
} else {
// ignore array and just return the one object
rc = arg;
}
delete data;
jsCallback.Call({ rc });
return Napi::Value();
}
2019-11-09 18:18:56 +01:00
}
2019-12-01 23:03:56 +01:00
ContextData::ContextData(Napi::Env env)
2019-12-09 14:57:21 +01:00
: m_hubConnectPromise(env),
m_indexerConnectPromise(env),
2021-02-12 21:38:06 +01:00
m_fullConnectPromise(env),
2021-02-14 19:44:28 +01:00
m_engine(new Engine(this))
2019-12-01 23:03:56 +01:00
{
}
2019-11-09 19:08:38 +01:00
void ContextData::setupBindings(Napi::Env env, Napi::Object exports)
2019-11-09 18:18:56 +01:00
{
2020-12-16 22:11:55 +01:00
auto m1 = Napi::PropertyDescriptor::Function(env, exports, "testConnect", contextData_testConnect, napi_default, this);
auto m2 = Napi::PropertyDescriptor::Function(env, exports, "testConnectHub", contextData_testConnectHub, napi_default, this);
auto m3 = Napi::PropertyDescriptor::Function(env, exports, "testConnectIndexer", contextData_testConnectIndexer, napi_default, this);
auto m4 = Napi::PropertyDescriptor::Function(env, exports, "connect", contextData_connect, napi_default, this);
auto m5 = Napi::PropertyDescriptor::Function(env, exports, "connectHub", contextData_connectHub, napi_default, this);
auto m6 = Napi::PropertyDescriptor::Function(env, exports, "connectIndexer", contextData_connectIndexer, napi_default, this);
auto m7 = Napi::PropertyDescriptor::Function(env, exports, "search", contextData_startSearch, napi_default, this);
auto m8 = Napi::PropertyDescriptor::Function(env, exports, "sendTransaction", contextData_sendTransaction, napi_default, this);
2019-11-09 18:18:56 +01:00
2020-12-16 22:11:55 +01:00
auto m9 = Napi::PropertyDescriptor::Function(env, exports, "subscribeToAddress", contextData_subscribeToAddress, napi_default, this);
auto m10 = Napi::PropertyDescriptor::Function(env, exports, "unsubscribeAddress", contextData_unsubscribeAddress, napi_default, this);
auto m11 = Napi::PropertyDescriptor::Function(env, exports, "sendMessage",
2020-07-17 15:46:49 +02:00
contextData_sendMessage, napi_default, this);
2021-02-15 10:46:56 +01:00
auto m12 = Napi::PropertyDescriptor::Function(env, exports, "stop",
contextData_shutdown, napi_default, this);
2021-02-26 19:37:16 +01:00
auto m13 = Napi::PropertyDescriptor::Function(env, exports, "parseAddress", contextData_parseAddress, napi_default, this);
2019-12-30 17:13:29 +01:00
2019-11-09 19:08:38 +01:00
auto p1 = Napi::PropertyDescriptor::Accessor(env, exports, "onConnectHub",
2019-12-09 14:57:21 +01:00
returnValue, setOnConnectedHub, napi_writable, this);
auto p2 = Napi::PropertyDescriptor::Accessor(env, exports, "onConnectIndexer",
2019-12-09 14:57:21 +01:00
returnValue, setOnConnectIndexer, napi_writable, this);
auto p3 = Napi::PropertyDescriptor::Accessor(env, exports, "onConnected",
returnValue, setOnConnected, napi_writable, this);
2019-12-30 17:13:29 +01:00
auto p4 = Napi::PropertyDescriptor::Accessor(env, exports, "onAddressChanged",
returnValue, setOnAddressChanged, napi_writable, this);
auto p5 = Napi::PropertyDescriptor::Accessor(env, exports, "onChainChanged",
returnValue, ::setOnChainChanged, napi_writable, this);
auto p6 = Napi::PropertyDescriptor::Accessor(env, exports, "logLevel",
2021-02-12 21:38:06 +01:00
getLogLevel, ::setLogLevel, napi_writable, this);
2019-11-09 18:18:56 +01:00
2019-11-28 23:58:56 +01:00
auto enum1 = Napi::PropertyDescriptor::Value("IncludeOffsetInBlock", Napi::Value::From(env, int(Blockchain::IncludeOffsetInBlock)));
auto enum2 = Napi::PropertyDescriptor::Value("IncludeInputs", Napi::Value::From(env, int(Blockchain::IncludeInputs)));
auto enum3 = Napi::PropertyDescriptor::Value("IncludeTxid", Napi::Value::From(env, int(Blockchain::IncludeTxId)));
2019-12-18 23:22:48 +01:00
auto enum4 = Napi::PropertyDescriptor::Value("IncludeFullTxData", Napi::Value::From(env, int(Blockchain::IncludeFullTransactionData)));
2019-11-28 23:58:56 +01:00
auto enum5 = Napi::PropertyDescriptor::Value("IncludeOutputs", Napi::Value::From(env, int(Blockchain::IncludeOutputs)));
auto enum6 = Napi::PropertyDescriptor::Value("IncludeOutputAmounts", Napi::Value::From(env, int(Blockchain::IncludeOutputAmounts)));
auto enum7 = Napi::PropertyDescriptor::Value("IncludeOutputScripts", Napi::Value::From(env, int(Blockchain::IncludeOutputScripts)));
2019-12-23 15:02:13 +01:00
auto enum8 = Napi::PropertyDescriptor::Value("IncludeOutputAddresses", Napi::Value::From(env, int(Blockchain::IncludeOutputAddresses)));
auto enum9 = Napi::PropertyDescriptor::Value("IncludeOutputScriptHash", Napi::Value::From(env, int(Blockchain::IncludeOutputScriptHash)));
auto enum10 = Napi::PropertyDescriptor::Value("IncludeTxFees", Napi::Value::From(env, int(Blockchain::IncludeTxFees)));
auto enum11 = Napi::PropertyDescriptor::Value("InfoLevel", Napi::Value::From(env, int(Log::InfoLevel)));
auto enum12 = Napi::PropertyDescriptor::Value("WarningLevel", Napi::Value::From(env, int(Log::WarningLevel)));
auto enum13 = Napi::PropertyDescriptor::Value("QuietLevel", Napi::Value::From(env, int(Log::CriticalLevel)));
auto enum14 = Napi::PropertyDescriptor::Value("SilentLevel", Napi::Value::From(env, int(Log::FatalLevel)));
2019-11-28 23:58:56 +01:00
2021-02-26 19:37:16 +01:00
exports.DefineProperties({m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13,
p1, p2, p3, p4, p5, p6,
2021-02-12 21:38:06 +01:00
enum1, enum2, enum3, enum4, enum5, enum6, enum7, enum8,
enum9, enum10, enum11, enum12, enum13, enum14,
2019-11-28 23:58:56 +01:00
});
Napi::Object jobEnum = Napi::Object::New(env);
auto jobVal1 = Napi::PropertyDescriptor::Value("LookupTxById", Napi::Value::From(env, int(Blockchain::LookupTxById)));
auto jobVal2 = Napi::PropertyDescriptor::Value("LookupByAddress", Napi::Value::From(env, int(Blockchain::LookupByAddress)));
auto jobVal3 = Napi::PropertyDescriptor::Value("LookupSpentTx", Napi::Value::From(env, int(Blockchain::LookupSpentTx)));
auto jobVal4 = Napi::PropertyDescriptor::Value("FetchTx", Napi::Value::From(env, int(Blockchain::FetchTx)));
auto jobVal5 = Napi::PropertyDescriptor::Value("FetchBlockHeader", Napi::Value::From(env, int(Blockchain::FetchBlockHeader)));
auto jobVal6 = Napi::PropertyDescriptor::Value("FetchBlockOfTx", Napi::Value::From(env, int(Blockchain::FetchBlockOfTx)));
auto jobVal7 = Napi::PropertyDescriptor::Value("FetchUTXOUnspent", Napi::Value::From(env, int(Blockchain::FetchUTXOUnspent)));
auto jobVal8 = Napi::PropertyDescriptor::Value("FetchUTXODetails", Napi::Value::From(env, int(Blockchain::FetchUTXODetails)));
2021-02-11 15:52:00 +01:00
auto jobVal9 = Napi::PropertyDescriptor::Value("FindTxInMempool", Napi::Value::From(env, int(Blockchain::FindTxInMempool)));
auto jobVal10 = Napi::PropertyDescriptor::Value("FindAddressInMempool", Napi::Value::From(env, int(Blockchain::FindAddressInMempool)));
jobEnum.DefineProperties({ jobVal1, jobVal2, jobVal3, jobVal4, jobVal5, jobVal6, jobVal7, jobVal8, jobVal9, jobVal10});
2019-11-28 23:58:56 +01:00
exports.Set("Job", jobEnum);
2019-11-09 18:18:56 +01:00
}
2021-02-14 19:44:28 +01:00
Napi::Value ContextData::connect(napi_env env, const std::string &hostname, Engine::Net net)
2019-11-09 18:18:56 +01:00
{
2021-02-14 19:44:28 +01:00
engine()->connect(hostname, net);
2019-12-09 14:57:21 +01:00
createPromiseCallback(env);
return m_fullConnectPromise.promise(env);
2019-11-09 18:18:56 +01:00
}
2019-12-09 14:57:21 +01:00
Napi::Value ContextData::connectHub(napi_env env, const std::string &hostname, int port)
2019-11-09 18:18:56 +01:00
{
2019-12-09 14:57:21 +01:00
if (port < 0 || port > 0xFFFF) {
Napi::TypeError::New(env, "Port out of range").ThrowAsJavaScriptException();
2019-12-09 14:57:21 +01:00
return Napi::Value();
}
2021-02-14 19:44:28 +01:00
m_engine->connectHub(hostname, port);
2020-07-27 19:26:05 +02:00
createPromiseCallback(env);
2019-12-09 14:57:21 +01:00
return m_hubConnectPromise.promise(env);
2019-11-09 18:18:56 +01:00
}
2019-12-09 14:57:21 +01:00
Napi::Value ContextData::connectIndexer(napi_env env, const std::string &hostname, int port)
2019-11-09 18:18:56 +01:00
{
2019-12-09 14:57:21 +01:00
if (port < 0 || port > 0xFFFF) {
Napi::TypeError::New(env, "Port out of range").ThrowAsJavaScriptException();
2019-12-09 14:57:21 +01:00
return Napi::Value();
}
2021-02-14 19:44:28 +01:00
m_engine->connectIndexer(hostname, port);
2020-07-27 19:26:05 +02:00
createPromiseCallback(env);
2019-12-09 14:57:21 +01:00
return m_indexerConnectPromise.promise(env);
2019-11-09 18:18:56 +01:00
}
2019-11-28 23:58:56 +01:00
Napi::Value ContextData::startSearch(const Napi::CallbackInfo &info)
{
if (info.Length() != 1) {
Napi::TypeError::New(info.Env(), "One arg (object literal) expected").ThrowAsJavaScriptException();
return Napi::Value();
}
auto request = info[0];
if (!request.IsObject()) {
Napi::TypeError::New(info.Env(), "Argument should be an object literal").ThrowAsJavaScriptException();
return Napi::Value();
}
2019-11-29 20:12:16 +01:00
try {
Search *searchObject = Search::create(request.As<Napi::Object>());
2021-02-14 19:44:28 +01:00
m_engine->start(searchObject);
return searchObject->promise(info.Env());
2020-12-16 22:24:53 +01:00
} catch (const Blockchain::ServiceUnavailableException &e) {
std::string error(e.what());
switch (e.service()) {
case Blockchain::TheHub: error += ": hub"; break;
case Blockchain::IndexerTxIdDb: error += ": TXID-DB"; break;
case Blockchain::IndexerAddressDb: error += ": Address-DB"; break;
case Blockchain::IndexerSpentDb: error += ": Spent-DB"; break;
}
error += ". Forgot to connect() ?";
Napi::TypeError::New(info.Env(), error).ThrowAsJavaScriptException();
return Napi::Value();
} catch (const std::runtime_error &e) {
2019-11-29 20:12:16 +01:00
Napi::TypeError::New(info.Env(), e.what()).ThrowAsJavaScriptException();
return Napi::Value();
}
2019-11-28 23:58:56 +01:00
}
Napi::Value ContextData::sendTransaction(const Napi::CallbackInfo &info)
{
if (info.Length() != 1) {
Napi::TypeError::New(info.Env(), "Missing transaction argument").ThrowAsJavaScriptException();
return Napi::Value();
}
Napi::Value txval = info[0];
2021-02-18 09:47:42 +01:00
Message message;
if (txval.IsTypedArray()) {
Napi::TypedArray data = txval.As<Napi::TypedArray>();
Napi::ArrayBuffer buffer = data.ArrayBuffer();
2021-02-18 09:47:42 +01:00
Streaming::MessageBuilder builder(m_engine->poolForThread(data.ByteLength() + 10));
builder.addByteArray(Api::LiveTransactions::Transaction,
static_cast<uint8_t*>(buffer.Data()) + data.ByteOffset(), data.ByteLength());
2021-02-18 09:47:42 +01:00
message = builder.message(Api::LiveTransactionService, Api::LiveTransactions::SendTransaction);
} else if (txval.IsString()) {
Napi::String string = txval.ToString();
try {
// assume hex encoding.
std::vector<char> bytes;
boost::algorithm::unhex(string.Utf8Value(), back_inserter(bytes));
2021-02-18 09:47:42 +01:00
Streaming::MessageBuilder builder(m_engine->poolForThread(bytes.size() + 10));
builder.add(Api::LiveTransactions::Transaction, bytes);
2021-02-18 09:47:42 +01:00
message = builder.message(Api::LiveTransactionService, Api::LiveTransactions::SendTransaction);
} catch (std::exception &e) {
Napi::TypeError::New(info.Env(), "Tx decoding failed; not hex.").ThrowAsJavaScriptException();
return Napi::Value();
}
// } else if(...) { // TODO support other formats?
} else {
Napi::TypeError::New(info.Env(), "Argument error, no tx found").ThrowAsJavaScriptException();
return Napi::Value();
}
2021-02-14 19:44:28 +01:00
if (!m_engine->isHubConnected()) {
Napi::TypeError::New(info.Env(), "Not connected to a hub").ThrowAsJavaScriptException();
return Napi::Value();
}
// we will need that to fulfill the promise
createPromiseCallback(info.Env());
const int jobId = m_nextNetworkJobId++;
auto iterator = m_networkJobs.insert(std::make_pair(jobId, Flowee::PromiseCallback(info.Env()))).first;
logInfo() << "Created sentTx promise with ID:" << jobId;
try {
message.setHeaderInt(Api::RequestId, jobId);
2021-02-14 19:44:28 +01:00
m_engine->sendMessage(message, Blockchain::TheHub);
return iterator->second.promise(info.Env());
} catch (const std::exception &e) {
logInfo() << " failed sendTransaction, not connected to a hub";
Napi::TypeError::New(info.Env(), "Not connected to a hub").ThrowAsJavaScriptException();
m_networkJobs.erase(iterator);
return Napi::Value();
}
}
2019-12-30 17:13:29 +01:00
Napi::Value ContextData::updateAddressMonitor(const Napi::CallbackInfo &info, ContextData::UpdateType type)
{
// we expect a string (or bytearray) for the address.
if (info.Length() != 1) {
Napi::TypeError::New(info.Env(), "Incorrect argument count").ThrowAsJavaScriptException();
return Napi::Value();
}
Napi::Array addresses;
if (info[0].IsArray()) {
addresses = info[0].As<Napi::Array>();
} else {
addresses = Napi::Array::New(info.Env());
addresses[uint32_t(0)] = info[0];
}
std::vector<Engine::SubscribedAddress> subscriptionAddresses;
bool rc = true;
2019-12-30 17:13:29 +01:00
for (uint32_t i = 0; i < addresses.Length(); ++i) {
Napi::Value addressVal = addresses[i];
std::string orig;
Streaming::ConstBuffer addressHash;
if (addressVal.IsString()) {
auto str = addressVal.ToString();
orig = str.Utf8Value();
addressHash = Flowee::parseAddress(str);
}
else if (addressVal.IsTypedArray()) {
Napi::TypedArray data = addressVal.As<Napi::TypedArray>();
Napi::ArrayBuffer buffer = data.ArrayBuffer();
if (buffer.ByteLength() != 32) {
Napi::TypeError::New(info.Env(), "Scripthash address-type has to be a sha256 hash").ThrowAsJavaScriptException();
return Napi::Value();
}
auto &pool = m_engine->poolForThread(32);
memcpy(pool.begin(), static_cast<uint8_t*>(buffer.Data()) + data.ByteOffset(), 32);
addressHash = pool.commit(32);
}
2019-12-30 17:13:29 +01:00
if (addressHash.isEmpty()) {
Napi::TypeError::New(info.Env(), "could not parse address").ThrowAsJavaScriptException();
return Napi::Value();
}
if (type == Subscribe) {
subscriptionAddresses.push_back({addressHash, orig});
2019-12-30 17:13:29 +01:00
} else {
assert(type == Unsubscribe);
if (!m_engine->unsubscribe(addressHash))
rc = false;
2019-12-30 17:13:29 +01:00
}
}
if (type == Subscribe)
m_engine->subscribe(subscriptionAddresses);
return Napi::Boolean::New(info.Env(), rc);
2019-12-30 17:13:29 +01:00
}
2021-02-14 19:44:28 +01:00
void ContextData::messageFromHub(const Message &message)
{
const int id = message.headerInt(Api::RequestId);
if (id > 0) {
// then likely this is the result of one of the network jobs. Those are handled in the NodeJS thread
2019-12-09 14:57:21 +01:00
NetMessageData *nmd = new NetMessageData();
nmd->data = this;
nmd->message = message;
if (!promiseCallback(nmd, contextData_handleNetMessageFinished))
delete nmd; // failed, cleanup after ourselves.
2019-12-30 17:13:29 +01:00
return;
}
if (message.serviceId() == Api::AddressMonitorService) {
if (message.messageId() == Api::AddressMonitor::SubscribeReply) {
Streaming::MessageParser parser(message.body());
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == Api::AddressMonitor::ErrorMessage) {
2021-02-12 21:38:06 +01:00
logWarning() << "AddressMonitor subscribe returned error:"
2019-12-30 17:13:29 +01:00
<< parser.stringData();
}
}
}
else if (message.messageId() == Api::AddressMonitor::TransactionFound
|| message.messageId() == Api::AddressMonitor::DoubleSpendFound) {
2021-02-17 21:02:06 +01:00
if (m_addressMonitorCallback && m_addressMonitorCallback->acquire()) {
2019-12-30 17:13:29 +01:00
NetMessageData *m = new NetMessageData();
m->message = message;
m->data = this;
2021-02-17 21:02:06 +01:00
m_addressMonitorCallback->f.NonBlockingCall(m, contextData_handleAM);
2019-12-30 17:13:29 +01:00
}
}
2019-12-09 14:57:21 +01:00
}
else if (message.serviceId() == Api::BlockNotificationService) {
if (message.messageId() == Api::BlockNotification::NewBlockOnChain
|| message.messageId() == Api::BlockNotification::BlocksRemoved) {
2021-02-17 21:02:06 +01:00
if (m_newBlockCallback && m_newBlockCallback->acquire()) {
NetMessageData *m = new NetMessageData();
m->message = message;
m->data = this;
m_newBlockCallback->f.NonBlockingCall(m, contextData_handleChainChanged);
}
}
}
2020-07-17 22:31:43 +02:00
else {
logInfo() << "Hub reply that is unrecognized";
Streaming::MessageParser::debugMessage(message);
}
}
2021-02-14 19:44:28 +01:00
bool ContextData::hubConnected(const std::string &hubVersion)
2020-05-20 12:06:21 +02:00
{
2021-02-14 19:44:28 +01:00
/*
* On connection complete we;
* 1. call back the function passed to the onConnectHub property.
* 2. potentially call back the function passed to the onAllConnected property.
* 3. resolve the hub promise.
* 4. potentially resolve the allconnected promise.
*
* 3 and 4 are done in the callback C++ methods.
*/
2021-02-17 21:02:06 +01:00
if (m_onHubConnect && m_onHubConnect->acquire()) {
2021-02-14 19:44:28 +01:00
HubVersion *hv = new HubVersion();
hv->floweeVersion = static_cast<char *>(malloc(hubVersion.size()));
strcpy(hv->floweeVersion, hubVersion.c_str());
hv->data = this;
2021-02-17 21:02:06 +01:00
m_onHubConnect->f.NonBlockingCall(hv, contextData_hubConnectedCallback);
2021-02-14 19:44:28 +01:00
return true;
2020-05-20 12:06:21 +02:00
}
2021-02-14 19:44:28 +01:00
return false; // allow for the usecase where only a hub is connected to without callback
}
bool ContextData::indexerConnected(const std::set<Blockchain::Service> &services)
{
/*
* On connection complete we;
* 1. call back the function passed to the onConnectIndexer property.
* 2. potentially call back the function passed to the onAllConnected property.
* 3. resolve the indexer promise.
* 4. potentially resolve the allconnected promise.
*
* 3 and 4 are done in the callback C++ methods.
*/
2021-02-17 21:02:06 +01:00
if (m_onIndexerConnect && m_onIndexerConnect->acquire()) {
2021-02-14 19:44:28 +01:00
IndexerServices *is = new IndexerServices;
is->services = services;
is->data = this;
2021-02-17 21:02:06 +01:00
m_onIndexerConnect->f.NonBlockingCall(is, contextData_IndexerConnectedCallback);
2021-02-14 19:44:28 +01:00
return true;
}
return false;
}
void ContextData::createPromiseCallback(Napi::Env env)
{
2021-02-17 21:41:30 +01:00
if (m_promiseCallback)
2021-02-14 19:44:28 +01:00
return;
// we only need it if no other callback exists.
2021-02-17 21:02:06 +01:00
if (!m_onHubConnect && !m_onIndexerConnect)
m_promiseCallback.reset(new Flowee::Callback(Napi::ThreadSafeFunction::New(env, Napi::Function(), "", 0, 1)));
2021-02-14 19:44:28 +01:00
}
void ContextData::startAllConnectedCallbacks()
{
/*
* In case both the indexer and hub are connected we need to do two things.
* 1. call back the function passed to the onAllConnected property.
* 2. resolve the allconnected promise.
*
* The second will be called from the first, but if we don't do the first then
* we need to find another callback to handle the promise.
*/
// then the "allConnected" callback etc need to be called too.
2021-02-17 21:02:06 +01:00
if (m_onAllConnected && m_onAllConnected-> acquire())
m_onAllConnected->f.NonBlockingCall(this, contextData_allConnectedCallback);
2021-02-14 19:44:28 +01:00
else
promiseCallback(this, contextData_fulfillFullConnectPromise);
2020-05-20 12:06:21 +02:00
}
void ContextData::handleMessageForNetPromise(Napi::Env env, const Message &message)
{
const int id = message.headerInt(Api::RequestId);
auto iterator = m_networkJobs.find(id);
if (iterator == m_networkJobs.end())
return;
Streaming::MessageParser parser(message.body());
try {
if (message.serviceId() == Api::LiveTransactionService && message.messageId() == Api::LiveTransactions::SendTransactionReply) {
Napi::Value hash = Napi::Boolean::From(env, true);
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == Api::LiveTransactions::GenericByteData) {
hash = Flowee::hashToString(env, parser.bytesDataBuffer());
break;
}
}
iterator->second.resolve(hash);
}
else if (message.serviceId() == Api::APIService && message.messageId() == Api::Meta::CommandFailed) {
std::string error = "unknown failure";
while (parser.next() == Streaming::FoundTag) {
if (parser.tag() == Api::Meta::FailedReason) {
error = parser.stringData();
break;
}
}
iterator->second.reject(env, error);
}
2020-07-17 22:31:43 +02:00
else {
// build an object copying the data from the message to be
// parsed in JS.
2020-07-18 14:36:37 +02:00
const Napi::Value KEY = Napi::String::New(env, "key");
const Napi::Value VALUE = Napi::String::New(env, "value");
const Napi::Value VALUE2 = Napi::String::New(env, "string");
2020-07-17 22:31:43 +02:00
Napi::Object msgJs = Napi::Object::New(env);
Napi::Object headers = Napi::Object::New(env);
2020-07-18 14:36:37 +02:00
Napi::Array body = Napi::Array::New(env);
2020-07-17 22:31:43 +02:00
for (auto iter = message.headerData().begin();
iter != message.headerData().end(); ++iter) {
if (iter->first == Network::ServiceId)
msgJs.Set("serviceId", Napi::Number::New(env, iter->second));
else if (iter->first == Network::MessageId)
msgJs.Set("messageId", Napi::Number::New(env, iter->second));
else if (iter->first > 11) {
headers.Set(Napi::String::New(env, std::to_string(iter->first)),
Napi::Number::New(env, iter->second));
}
}
msgJs.Set("header", headers);
2020-07-18 14:36:37 +02:00
int arrayIndex = 0;
2020-07-17 22:31:43 +02:00
while (parser.next() == Streaming::FoundTag) {
2020-07-18 14:36:37 +02:00
Napi::Value key = Napi::String::New(env, std::to_string(parser.tag()));
Napi::Value value;
2021-02-17 15:34:52 +01:00
if (parser.isInt())
value = Napi::Number::New(env, parser.intData());
else if (parser.isLong())
2020-07-18 14:36:37 +02:00
value = Napi::Number::New(env, parser.longData());
else if (parser.isString())
value = Napi::String::New(env, parser.stringData());
else if (parser.isBool())
value = Napi::Boolean::New(env, parser.boolData());
else if (parser.isByteArray())
value = Flowee::wrap(env, parser.bytesDataBuffer());
else if (parser.isDouble())
value = Napi::Number::New(env, parser.doubleData());
if (!value.IsEmpty()) {
if (parser.tag() != 0) // not the separator
msgJs.Set(key, value);
Napi::Object pair = Napi::Object::New(env);
pair.Set(KEY, key);
pair.Set(VALUE, value);
body[arrayIndex++] = pair;
2020-07-27 11:43:55 +02:00
if (parser.isByteArray() && parser.dataLength() == 32) {
2020-07-18 14:36:37 +02:00
value = Flowee::hashToString(env, parser.bytesDataBuffer());
pair.Set(VALUE2, value);
key = Napi::String::New(env, std::to_string(parser.tag()) + "str");
msgJs.Set(key, value);
}
2020-07-17 22:31:43 +02:00
}
}
msgJs.Set("body", body);
iterator->second.resolve(msgJs);
}
} catch (const std::exception &e) {
2021-02-12 21:38:06 +01:00
logWarning() << "internal error:" << e;
2019-12-09 14:57:21 +01:00
}
// a second resolve has no effect, so lets just do it to be sure our user doesn't hang.
iterator->second.resolve(env, "result lost");
m_networkJobs.erase(iterator);
2019-12-09 14:57:21 +01:00
}
2020-07-17 15:46:49 +02:00
Napi::Value ContextData::sendJsMessage(const Napi::CallbackInfo &info)
{
2020-07-17 22:31:43 +02:00
logDebug() << "sendjsmessage called";
2020-07-17 15:46:49 +02:00
if (info.Length() < 0) {
Napi::TypeError::New(info.Env(), "Missing message arg").ThrowAsJavaScriptException();
return Napi::Value();
}
auto request = info[0];
if (!request.IsObject()) {
Napi::TypeError::New(info.Env(), "Argument should be an object literal").ThrowAsJavaScriptException();
return Napi::Value();
}
auto jsMessage = request.As<Napi::Object>();
if (!jsMessage.Get("header").IsObject() || !jsMessage.Get("body").IsObject()) {
Napi::TypeError::New(info.Env(), "Bad message").ThrowAsJavaScriptException();
return Napi::Value();
}
auto header = jsMessage.Get("header").As<Napi::Object>();
Napi::Value serviceIdVal = header.Get("serviceId");
Napi::Value messageIdVal = header.Get("messageId");
if (!serviceIdVal.IsNumber() || !messageIdVal.IsNumber()) {
Napi::TypeError::New(info.Env(), "Missing 'serviceId' or 'messageId' in header").ThrowAsJavaScriptException();
return Napi::Value();
}
auto body = jsMessage.Get("body").As<Napi::Object>();
auto keys = body.GetPropertyNames();
2021-02-18 09:47:42 +01:00
int size = 0;
for (uint32_t i = 0; i < keys.Length(); ++i) {
Napi::Value prop = keys[i];
assert(prop.IsString());
2021-02-26 19:37:16 +01:00
std::string propStr = prop.ToString().Utf8Value();
Napi::Value value = body[propStr];
if (value.IsNumber()) {
size += 10;
}
else if (value.IsString()) {
size += value.ToString().Utf8Value().size() + 5;
}
else if (value.IsBoolean()) {
size += 2;
}
else if (value.IsBuffer()) {
Napi::Buffer<char> buf = value.As<Napi::Buffer<char>>();
size += buf.Length() + 10;
}
else if (value.IsArray()) {
Napi::Array values = value.As<Napi::Array>();
size += values.GetPropertyNames().Length() * 40;
}
2021-02-18 09:47:42 +01:00
}
Streaming::MessageBuilder builder(m_engine->poolForThread(size + 10));
2020-07-17 15:46:49 +02:00
for (uint32_t i = 0; i < keys.Length(); ++i) {
Napi::Value prop = keys[i];
assert(prop.IsString());
std::string propStr = prop.ToString().Utf8Value();
try {
2020-07-18 14:36:37 +02:00
int key_ = std::stoi(propStr); // throws if not a number
if (key_ < 0) // negative numbers not allowed for key.
continue;
const uint32_t key = static_cast<uint32_t>(key_);
2020-07-17 15:46:49 +02:00
Napi::Value value = body[propStr];
if (value.IsNumber()) {
2020-07-18 14:36:37 +02:00
int64_t num = value.ToNumber().Int64Value();
2020-07-17 15:46:49 +02:00
if (num < 0)
builder.add(key, static_cast<int32_t>(num));
else
builder.add(key, static_cast<uint64_t>(num));
}
else if (value.IsString()) {
std::string str = value.ToString().Utf8Value();
builder.add(key, str);
}
2020-07-18 14:36:37 +02:00
else if (value.IsBoolean()) {
builder.add(key, value.ToBoolean().Value());
}
else if (value.IsBuffer()) {
Napi::Buffer<char> buf = value.As<Napi::Buffer<char>>();
builder.addByteArray(key, buf.Data(), buf.Length());
}
2021-02-26 19:37:16 +01:00
else if (value.IsArray()) {
Napi::Array array = value.As<Napi::Array>();
auto rowNames = array.GetPropertyNames();
for (uint32_t j = 0; j < rowNames.Length(); ++j) {
Napi::Value val2 = array[j];
if (val2.IsString()) {
auto rc = Flowee::parseAddress(val2.ToString());
if (rc.isEmpty()) {
Napi::TypeError::New(info.Env(), "Failed to parse array value").ThrowAsJavaScriptException();
return Napi::Value();
}
builder.add(key, rc);
} else if (val2.IsTypedArray()) {// likely a wrapped buffer.
Napi::TypedArray data = val2.As<Napi::TypedArray>();
Napi::ArrayBuffer buffer = data.ArrayBuffer();
if (buffer.ByteLength() > 37) {
Napi::TypeError::New(info.Env(), "Unexpected large value as array value").ThrowAsJavaScriptException();
return Napi::Value();
}
builder.addByteArray(key,
static_cast<uint8_t*>(buffer.Data()) + data.ByteOffset(), data.ByteLength());
}
}
}
2020-07-17 15:46:49 +02:00
else
2021-02-12 21:38:06 +01:00
logWarning() << "Failed to process message-key" << propStr;
2020-07-17 15:46:49 +02:00
} catch (const std::exception &) {} // silently ignore invalid body items
}
const int serviceId = serviceIdVal.As<Napi::Number>().Int32Value();
const int messageId = messageIdVal.As<Napi::Number>().Int32Value();
auto message = builder.message(serviceId, messageId);
// find if there are additional items in the header to add.
keys = header.GetPropertyNames();
for (uint32_t i = 0; i < keys.Length(); ++i) {
Napi::Value prop = keys[i];
assert(prop.IsString());
std::string key = prop.ToString().Utf8Value();
try {
int headerKey = std::stoi(key);
2020-07-17 22:31:43 +02:00
if (headerKey > 11) { // everything lower is not for users
2020-07-17 15:46:49 +02:00
Napi::Value value = header[key];
if (value.IsNumber())
message.setHeaderInt(headerKey, value.As<Napi::Number>().Int32Value());
else
2021-02-12 21:38:06 +01:00
logWarning() << "Header" << key << "has value which is not a number";
2020-07-17 15:46:49 +02:00
}
} catch (const std::exception &) {} // silently ignore invalid header items
}
2020-07-17 22:31:43 +02:00
// we will need that initialized to fulfill the promise
createPromiseCallback(info.Env());
const int jobId = m_nextNetworkJobId++;
message.setHeaderInt(Api::RequestId, jobId);
auto iterator = m_networkJobs.insert(std::make_pair(jobId, Flowee::PromiseCallback(info.Env()))).first;
logInfo() << "Created sentTx promise with ID:" << jobId;
2020-07-17 15:46:49 +02:00
Blockchain::Service s = Blockchain::TheHub;
if (serviceId == Api::IndexerService) {
switch (messageId) {
case Api::Indexer::FindAddress:
s = Blockchain::IndexerAddressDb;
break;
case Api::Indexer::FindSpentOutput:
s = Blockchain::IndexerSpentDb;
break;
default:
s = Blockchain::IndexerTxIdDb;
break;
}
}
2021-02-14 19:44:28 +01:00
m_engine->sendMessage(message, s);
2020-07-17 15:46:49 +02:00
2020-07-17 22:31:43 +02:00
return iterator->second.promise(info.Env());
2020-07-17 15:46:49 +02:00
}
2021-02-15 10:46:56 +01:00
2021-02-20 11:04:15 +01:00
void ContextData::shutdown(Napi::Env env)
2021-02-15 10:46:56 +01:00
{
m_engine.reset();
2021-02-18 09:47:42 +01:00
if (m_onIndexerConnect)
m_onIndexerConnect->free();
if (m_onHubConnect)
m_onHubConnect->free();
if (m_onAllConnected)
m_onAllConnected->free();
if (m_addressMonitorCallback)
m_addressMonitorCallback->free();
if (m_newBlockCallback)
2021-02-19 18:22:53 +01:00
m_newBlockCallback->free();
if (m_promiseCallback)
2021-02-18 09:47:42 +01:00
m_promiseCallback->free();
2021-02-20 11:04:15 +01:00
const Napi::String msg = Napi::String::New(env, "stopped");
2021-02-26 19:37:16 +01:00
for (auto i : m_networkJobs) {
i.second.reject(msg);
}
m_networkJobs.clear();
2021-02-20 11:04:15 +01:00
m_hubConnectPromise.reject(msg);
m_indexerConnectPromise.reject(msg);
m_fullConnectPromise.reject(msg);
2021-02-15 10:46:56 +01:00
}