Files
js/Flowee.cpp
T

293 lines
9.5 KiB
C++
Raw Permalink Normal View History

2019-11-09 19:08:38 +01:00
/*
* This file is part of the Flowee project
* Copyright (C) 2019 Tom Zander <tomz@freedommail.ch>
*
* 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-29 22:12:08 +01:00
2019-11-30 19:46:30 +01:00
// #undef NDEBUG // make assert do something
2019-11-29 22:12:08 +01:00
#include "Flowee.h"
2019-11-09 18:18:56 +01:00
#include "ContextData.h"
2019-12-08 23:20:41 +01:00
#include <boost/algorithm/hex.hpp>
2019-12-30 17:13:29 +01:00
#include <base58.h>
2019-12-08 23:20:41 +01:00
#include <cashaddr.h>
2019-12-30 17:13:29 +01:00
#include <utilstrencodings.h>
2019-11-09 18:18:56 +01:00
2019-11-30 19:46:30 +01:00
namespace {
/* Javascripts ArrayBuffer is almost the same as Flowee's ConstBuffer
* and technically it is posible to create one ArrayBuffer for one
* of Flowee's shared buffers. Which may be shared between many ConstBuffer
* instances and subsequently, many Javascript UInt8Array instances.
*
* To do this, however, I need to somehow figure out to reuse a BufferWrap
* when I see the same share pointer used in a different ConstBuffer.
* The correct solution for this is to have a map on the context object
* to remember the Napi::ArrayBuffer instances and make the BufferWrap
* update that map.
*
* This is left for the future, as we already have zero copy, all the way
* from the network layer to the Javascript layer.
*
* Which is pretty decent.
*
* Refactoring stuff to pass in the context everywhere is a lot of work
* and then profiling it to see if the map overhead is keeping the whole
* actually beneficial is a lot more work...
*/
struct BufferWrap
{
BufferWrap(const std::shared_ptr<char> &buffer) : buf(buffer) {}
std::shared_ptr<char> buf;
};
void destructor(Napi::Env, void *bufPtr, void *bufWrap)
{
(void)bufPtr;
BufferWrap *w = reinterpret_cast<BufferWrap*>(bufWrap);
assert(w);
assert(bufPtr == w->buf.get());
delete w;
}
}
Napi::Uint8Array Flowee::wrap(Napi::Env env, const Streaming::ConstBuffer &buffer)
{
if (buffer.isEmpty())
return Napi::Uint8Array::New(env, 0);
BufferWrap *wrap = new BufferWrap(buffer.internal_buffer());
auto buf = Napi::ArrayBuffer::New(env, const_cast<void*>(reinterpret_cast<const void*>(buffer.begin())),
static_cast<size_t>(buffer.size()), destructor, wrap);
return Napi::Uint8Array::New(env, static_cast<size_t>(buffer.size()), buf, 0);
}
2019-12-08 23:20:41 +01:00
char nibbleToChar(uint8_t k) {
k = k & 0xF;
if (k < 10)
return char('0' + k);
return char('a' + k - 10);
}
2019-11-30 19:46:30 +01:00
2019-12-08 23:20:41 +01:00
// custom made since Bitcoin Cash likes the order to be reversed on printing.
Napi::String Flowee::hashToString(Napi::Env env, const Streaming::ConstBuffer &buffer)
2019-11-29 22:12:08 +01:00
{
2019-12-08 23:20:41 +01:00
if (buffer.isEmpty())
return Napi::String::New(env, "");
assert(buffer.size() == 32);
if (buffer.size() != 32)
throw std::runtime_error("Internal error (hash invalid size)");
char buf[65];
char *ptr = buf;
for (int i = 31; i >= 0; --i) {
uint8_t byte = static_cast<uint8_t>(buffer.begin()[i]);
*ptr = nibbleToChar(byte >> 4);
++ptr;
*ptr = nibbleToChar(byte);
++ptr;
}
buf[64] = 0;
return Napi::String::New(env, buf);
}
Napi::Object Flowee::populateTransaction(Napi::Env env, const Blockchain::Transaction *transaction, bool binaryHash)
{
using boost::algorithm::hex;
2019-11-29 22:12:08 +01:00
Napi::Object tx = Napi::Object::New(env);
2019-11-30 19:46:30 +01:00
tx.Set("blockHeight", Napi::Number::From(env, transaction->blockHeight));
tx.Set("offsetInBlock", Napi::Number::From(env, transaction->offsetInBlock));
2019-12-08 19:01:24 +01:00
tx.Set("jobId", Napi::Number::From(env, transaction->jobId));
2019-11-30 19:46:30 +01:00
tx.Set("isCoinbase", Napi::Boolean::From(env, transaction->isCoinbase()));
tx.Set("fullTxData", wrap(env, transaction->fullTxData));
2019-12-08 23:20:41 +01:00
tx.Set("txid",
binaryHash ? wrap(env, transaction->txid) : hashToString(env, transaction->txid));
2019-11-29 22:12:08 +01:00
2019-11-30 19:46:30 +01:00
Napi::Array inputs = Napi::Array::New(env, transaction->inputs.size());
for (size_t i = 0 ; i < transaction->inputs.size(); ++i) {
const Blockchain::Input &input = transaction->inputs.at(i);
Napi::Object in = Napi::Object::New(env);
2019-12-08 23:20:41 +01:00
in.Set("outputIndex", Napi::Number::From(env, input.outIndex));
2019-11-30 19:46:30 +01:00
2019-12-08 23:20:41 +01:00
in.Set("previousTxid",
binaryHash ? wrap(env, input.prevTxId) : hashToString(env, input.prevTxId));
std::string is;
hex(input.inputScript.begin(), input.inputScript.end(), back_inserter(is));
2019-12-18 23:22:48 +01:00
in.Set("script", Napi::String::New(env, is));
in.Set("script-array", wrap(env, input.inputScript));
2019-11-30 19:46:30 +01:00
inputs[uint32_t(i)] = in;
2019-11-29 22:12:08 +01:00
}
2019-11-30 19:46:30 +01:00
tx.Set("inputs", inputs);
2019-11-29 22:12:08 +01:00
2019-11-30 19:46:30 +01:00
Napi::Array outputs = Napi::Array::New(env, transaction->outputs.size());
for (size_t i = 0 ; i < transaction->outputs.size(); ++i) {
const Blockchain::Output &output = transaction->outputs.at(i);
Napi::Object out = Napi::Object::New(env);
out.Set("index", Napi::Number::From(env, output.index));
out.Set("amount", Napi::Number::From(env, output.amount));
switch (output.type) {
case Blockchain::Output::Nothing: break;
2019-12-08 23:20:41 +01:00
case Blockchain::Output::FullScript: {
out.Set("script-array", wrap(env, output.outScript));
std::string s;
hex(output.outScript.begin(), output.outScript.end(), back_inserter(s));
out.Set("script", Napi::String::New(env, s));
2019-11-30 19:46:30 +01:00
break;
2019-12-08 23:20:41 +01:00
}
case Blockchain::Output::OnlyAddress: {
2019-12-23 15:02:57 +01:00
// TODO we assume we use main-chain addresses here.
// maybe useful to ask the hub which chain it is on?
const std::string cashAddress = CashAddress::encodeCashAddr("bitcoincash", { CashAddress::PUBKEY_TYPE,
std::vector<uint8_t>(output.outScript.begin(), output.outScript.end()) });
2019-12-08 23:20:41 +01:00
out.Set("address", cashAddress);
out.Set("address-array", wrap(env, output.outScript));
2019-11-30 19:46:30 +01:00
break;
2019-12-08 23:20:41 +01:00
}
2019-11-30 19:46:30 +01:00
default:
assert(false);
}
outputs[uint32_t(i)] = out;
}
tx.Set("outputs", outputs);
2019-11-29 22:12:08 +01:00
return tx;
}
2019-12-09 14:57:21 +01:00
Flowee::PromiseCallback::PromiseCallback(Napi::Env env)
: p(Napi::Promise::Deferred::New(env))
{
}
void Flowee::PromiseCallback::resolve(Napi::Value value)
{
if (!present)
return;
assert(!value.IsNull()); // null values make Resolve below throw
present = false;
p.Resolve(value);
}
Napi::Promise Flowee::PromiseCallback::promise(Napi::Env env)
{
p = Napi::Promise::Deferred::New(env);
present = true;
return p.Promise();
}
void Flowee::PromiseCallback::reject(Napi::Value value)
{
if (!present)
return;
assert(!value.IsNull()); // null values make Reject below throw
present = false;
p.Reject(value);
}
2019-12-30 17:13:29 +01:00
Streaming::ConstBuffer Flowee::hexStringToBuffer(const std::string &hash)
{
size_t i = 0;
if (hash.size() == 66) {
if (hash[0] == '0' && (hash[1] == 'x' || hash[1] == 'X')) {
i = 2;
} else {
return Streaming::ConstBuffer(); // invalid input
}
} else if (hash.size() != 64) { // invalid input
return Streaming::ConstBuffer(); // invalid input
}
Streaming::BufferPool pool(32);
int i2 = 31; // back to front
while (i < hash.size()) {
const int8_t k = hash[i];
const uint8_t v = static_cast<uint8_t>(HexDigit(k));
if (k > 'f' || v == 0xFF)
return Streaming::ConstBuffer(); // invalid input
if ((i % 2) == 0) {
pool.begin()[i2] = static_cast<char>(v << 4);
} else {
pool.begin()[i2--] += v;
}
++i;
}
return pool.commit(32);
}
Streaming::ConstBuffer Flowee::parseAddress(const Napi::String &input, bool *isHash)
{
// this can be either a hex string, or an address. Either way, utf8 should suffice.
std::string str = input.ToString().Utf8Value();
str.erase(remove_if(str.begin(), str.end(), isspace), str.end()); // trim
auto rc = hexStringToBuffer(str);
if (!rc.isEmpty()) {
if (isHash)
*isHash = true;
return rc;
}
CashAddress::Content c = CashAddress::decodeCashAddrContent(str, "bitcoincash");
bool ok = !c.hash.empty();
if (!ok) { // try to fall back to legacy address encoding (btc compatible)
CBase58Data old;
if (old.SetString(str)) {
c.hash = old.data();
ok = true;
if (old.isMainnetPkh())
c.type = CashAddress::PUBKEY_TYPE;
else if (old.isMainnetSh())
c.type = CashAddress::SCRIPT_TYPE;
else
ok = false;
}
}
if (ok) {
if (isHash)
*isHash = false;
return CashAddress::createHashedOutputScript(c);
}
return Streaming::ConstBuffer();
}
2019-11-29 22:12:08 +01:00
2019-11-09 19:08:38 +01:00
Napi::Object setupBindings(Napi::Env env, Napi::Object exports)
2019-11-09 18:18:56 +01:00
{
2019-12-01 23:03:56 +01:00
ContextData *data = new ContextData(env);
2019-11-09 18:18:56 +01:00
data->setupBindings(env, exports);
2019-11-29 22:12:08 +01:00
/*
2019-11-30 19:46:30 +01:00
// uncomment this to have full debug-level logging.
2019-11-29 20:12:16 +01:00
auto *logger = Log::Manager::instance();
logger->clearChannels();
Log::Verbosity v = Log::WarningLevel;
v = Log::DebugLevel;
logger->clearLogLevels(v);
logger->addConsoleChannel();
2019-11-29 22:12:08 +01:00
*/
2019-11-09 18:18:56 +01:00
return exports;
}
2019-12-08 23:20:41 +01:00
2019-11-09 19:08:38 +01:00
NODE_API_MODULE(flowee, setupBindings)