diff --git a/lib/bitcoind.js b/lib/bitcoind.js index 3f1fc431..f565e22c 100644 --- a/lib/bitcoind.js +++ b/lib/bitcoind.js @@ -360,6 +360,108 @@ Transaction.prototype.print = function() { ; }; +Transaction.prototype.toHex = function() { + return this.hex = this.hex || Transaction.toHex(this); +}; + +Transaction.toHex = function(tx) { +}; + +/** + * Broadcast TX + */ + +Bitcoin._broadcastTx = function(tx, options, callback) { + if (!callback) { + callback = options; + options = null; + } + + if (!options) { + options = {}; + } + + options.overrideFees = options.overrideFees || false; + + return bitcoindjs.broadcastTx(tx, options.overrideFees, callback); +}; + +Transaction.binary = function(tx) { + var p = []; + var off = utils.writeU32(p, tx.nVersion, 0); + off += utils.varint(p, tx.vin.length, off); + + for (var i = 0; i < tx.vin.length; i++) { + var input = tx.vin[i]; + + off += utils.copy(utils.toArray(input.out.hash, 'hex'), p, off, true); + off += utils.writeU32(p, input.out.index, off); + + var s = script.encode(input.script); + off += utils.varint(p, s.length, off); + off += utils.copy(s, p, off, true); + + off += utils.writeU32(p, input.seq, off); + } + + off += utils.varint(p, tx.vout.length, off); + for (var i = 0; i < tx.vout.length; i++) { + var output = tx.vout[i]; + + // Put LE value + var value = output.value.toArray().slice().reverse(); + assert(value.length <= 8); + off += utils.copy(value, p, off, true); + for (var j = value.length; j < 8; j++, off++) + p[off] = 0; + + var s = script.encode(output.script); + off += utils.varint(p, s.length, off); + off += utils.copy(s, p, off, true); + } + off += utils.writeU32(p, tx.nLockTime, off); + + return p; +}; + +var script = {}; + +script.encode = function encode(s) { + if (!s) + return []; + var opcodes = constants.opcodes; + var res = []; + for (var i = 0; i < s.length; i++) { + var instr = s[i]; + + // Push value to stack + if (Array.isArray(instr)) { + if (instr.length === 0) { + res.push(0); + } else if (instr.length === 1 && 0 < instr[0] && instr[0] <= 16) { + res.push(0x50 + instr[0]); + } else if (1 <= instr.length && instr.length <= 0x4b) { + res = res.concat(instr.length, instr); + } else if (instr.length <= 0xff) { + res = res.concat(opcodes.pushdata1, instr.length, instr); + } else if (instr.length <= 0xffff) { + res.push(opcodes.pushdata2); + utils.writeU16(res, instr.length, res.length); + res = res.concat(instr); + } else { + res.push(opcodes.pushdata4); + utils.writeU32(res, instr.length, res.length); + res = res.concat(instr); + } + continue; + } + + res.push(opcodes[instr] || instr); + } + + return res; +}; + /** * Utils */ @@ -385,6 +487,100 @@ utils.forEach = function(obj, iter, done) { }); }; +utils.writeU16 = function writeU16(dst, num, off) { + if (!off) + off = 0; + dst[off] = num & 0xff; + dst[off + 1] = (num >>> 8) & 0xff; + return 2; +}; + +utils.writeU32 = function writeU32(dst, num, off) { + if (!off) + off = 0; + dst[off] = num & 0xff; + dst[off + 1] = (num >>> 8) & 0xff; + dst[off + 2] = (num >>> 16) & 0xff; + dst[off + 3] = (num >>> 24) & 0xff; + return 4; +}; + +utils.writeU64 = function writeU64(dst, num, off) { + if (!off) + off = 0; + + num = new bn(num).maskn(64).toArray(); + while (num.length < 8) + num.unshift(0); + + num.reverse().forEach(function(ch) { + dst[off++] = ch; + }); + + var i = num.length; + while (i--) + dst[off++] = num[i]; + + return 8; +}; + +utils.writeU16BE = function writeU16BE(dst, num, off) { + if (!off) + off = 0; + dst[off] = (num >>> 8) & 0xff; + dst[off + 1] = num & 0xff; + return 2; +}; + +utils.writeU32BE = function writeU32BE(dst, num, off) { + if (!off) + off = 0; + dst[off] = (num >>> 24) & 0xff; + dst[off + 1] = (num >>> 16) & 0xff; + dst[off + 2] = (num >>> 8) & 0xff; + dst[off + 3] = num & 0xff; + return 4; +}; + +utils.writeU64BE = function writeU64BE(dst, num, off) { + if (!off) + off = 0; + + num = new bn(num).maskn(64).toArray(); + while (num.length < 8) + num.unshift(0); + + for (var i = 0; i < num.length; i++) + dst[off++] = num[i]; + + return 8; +}; + +utils.varint = function(arr, value, off) { + if (!off) + off = 0; + if (value < 0xfd) { + arr[off] = value; + return 1; + } else if (value <= 0xffff) { + arr[off] = 0xfd; + arr[off + 1] = value & 0xff; + arr[off + 2] = value >>> 8; + return 3; + } else if (value <= 0xffffffff) { + arr[off] = 0xfe; + arr[off + 1] = value & 0xff; + arr[off + 2] = (value >>> 8) & 0xff; + arr[off + 3] = (value >>> 16) & 0xff; + arr[off + 4] = value >>> 24; + return 5; + } else { + arr[off] = 0xff; + utils.writeU64(arr, value, off + 1); + return 9; + } +}; + /** * Expose */ diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index 5b8a0463..d999e941 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -125,6 +125,7 @@ NAN_METHOD(GetBlock); NAN_METHOD(GetTx); NAN_METHOD(PollBlocks); NAN_METHOD(PollMempool); +NAN_METHOD(BroadcastTx); static void async_start_node_work(uv_work_t *req); @@ -168,6 +169,12 @@ async_poll_mempool(uv_work_t *req); static void async_poll_mempool_after(uv_work_t *req); +static void +async_broadcast_tx(uv_work_t *req); + +static void +async_broadcast_tx_after(uv_work_t *req); + static inline void ctx_to_js(const CTransaction& tx, uint256 hashBlock, Local entry); @@ -255,6 +262,16 @@ struct async_poll_mempool_data { Persistent callback; }; +/** + * async_broadcast_tx + */ + +struct async_broadcast_tx { + std::string err_msg; + boost::string tx_hex; + bool override_fees; + Persistent callback; +}; /** * StartBitcoind @@ -928,6 +945,138 @@ async_poll_mempool_after(uv_work_t *req) { delete req; } +/** + * BroadcastTx(tx, override_fees, callback) + * bitcoind.broadcastTx(tx, override_fees, callback) + */ + +NAN_METHOD(BroadcastTx) { + NanScope(); + + if (args.Length() < 3 + || !args[0]->IsObject() + || !args[1]->IsBool() + || !args[2]->IsFunction()) { + return NanThrowError( + "Usage: bitcoindjs.broadcastTx(tx, override_fees, callback)"); + } + + Local js_tx = Local::Cast(args[0]); + Local callback = Local::Cast(args[2]); + + String::Utf8Value tx_hex_(js_tx->Get(NanNew("hex"))->ToString()); + std::string tx_hex = std::string(*tx_hex_); + if (tx_hex[1] != 'x') { + tx_hex = "0x" + tx_hex; + } + boost::string strHex(tx_hex); + + async_broadcast_tx *data = new async_broadcast_tx(); + data->tx_hex = strHex; + data->override_fees = args[1]->ToBoolean()->IsTrue(); + data->err_msg = std::string(""); + data->callback = Persistent::New(callback); + + uv_work_t *req = new uv_work_t(); + req->data = data; + + int status = uv_queue_work(uv_default_loop(), + req, async_broadcast_tx, + (uv_after_work_cb)async_broadcast_tx_after); + + assert(status == 0); + + NanReturnValue(Undefined()); +} + +static void +async_broadcast_tx(uv_work_t *req) { + async_poll_blocks_data* data = static_cast(req->data); + + // parse hex string from parameter + // vector txData(ParseHexV(params[0], "parameter")); + CDataStream ssData(data->tx_hex, SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + + bool fOverrideFees = false; + if (data->override_fees) { + fOverrideFees = true; + } + + // deserialize binary data stream + try { + ssData >> tx; + } catch (std::exception &e) { + data->err_msg = std::string("TX decode failed"); + return; + } + + uint256 hashTx = tx.GetHash(); + + bool fHave = false; + CCoinsViewCache &view = *pcoinsTip; + CCoins existingCoins; + { + fHave = view.GetCoins(hashTx, existingCoins); + if (!fHave) { + // push to local node + CValidationState state; + if (!AcceptToMemoryPool(mempool, state, tx, false, NULL, !fOverrideFees)) { + data->err_msg = std::string("TX rejected"); + return; + } + } + } + + if (fHave) { + if (existingCoins.nHeight < 1000000000) { + data->err_msg = std::string("transaction already in block chain"); + return; + } + // Not in block, but already in the memory pool; will drop + // through to re-relay it. + } else { + SyncWithWallets(hashTx, tx, NULL); + } + + RelayTransaction(tx, hashTx); + + data->tx_hash = hashTx.GetHex(); +} + +static void +async_broadcast_tx_after(uv_work_t *req) { + NanScope(); + async_poll_blocks_data* data = static_cast(req->data); + + if (!data->err_msg.empty()) { + Local err = Exception::Error(String::New(data->err_msg.c_str())); + const unsigned argc = 1; + Local argv[argc] = { err }; + TryCatch try_catch; + data->callback->Call(Context::GetCurrent()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } else { + const unsigned argc = 2; + Local argv[argc] = { + Local::New(Null()), + Local::New(data->tx_hash) + }; + TryCatch try_catch; + data->callback->Call(Context::GetCurrent()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } + + data->callback.Dispose(); + + delete data; + delete req; +} + /** * Conversions */ @@ -1139,6 +1288,155 @@ ctx_to_js(const CTransaction& tx, uint256 hashBlock, Local entry) { } } +#if 0 +static inline void +hex_to_ctx(string strHex, const CTransaction& tx) { + CDataStream stream(ParseHex(strHex), SER_NETWORK, PROTOCOL_VERSION); + // CTransaction tx; + stream >> tx; +} + +static inline void +js_to_ctx(Local entry, const CTransaction& tx, uint256 hashBlock) { + String::Utf8Value tx_hex_(entry->Get(NanNew("hex"))->ToString()); + std::string tx_hex = std::string(*txHex_); + if (tx_hex[1] != 'x') { + tx_hex = "0x" + tx_hex; + } + // std::string tx_hex = data->tx_hex; + // uint256 hash(tx_hex); + boost::string strHex(tx_hex); + // CTransaction tx; + hex_to_ctx(strHex, tx); +} + +static inline void +js_to_ctx(Local entry, const CTransaction& tx, uint256 hashBlock) { + // entry->Set(NanNew("hex"), NanNew(strHex)); + entry->Set(NanNew("txid"), NanNew(tx.GetHash().GetHex())); + entry->Set(NanNew("version"), NanNew(tx.nVersion)); + entry->Set(NanNew("locktime"), NanNew(tx.nLockTime)); + + Local vin = NanNew(); + int vi = 0; + BOOST_FOREACH(const CTxIn& txin, tx.vin) { + Local in = NanNew(); + if (tx.IsCoinBase()) { + in->Set(NanNew("coinbase"), NanNew(HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + } else { + in->Set(NanNew("txid"), NanNew(txin.prevout.hash.GetHex())); + in->Set(NanNew("vout"), NanNew((boost::int64_t)txin.prevout.n)); + Local o = NanNew(); + o->Set(NanNew("asm"), NanNew(txin.scriptSig.ToString())); + o->Set(NanNew("hex"), NanNew(HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + in->Set(NanNew("scriptSig"), o); + } + in->Set(NanNew("sequence"), NanNew((boost::int64_t)txin.nSequence)); + vin->Set(vi, in); + vi++; + } + entry->Set(NanNew("vin"), vin); + + Local vout = NanNew(); + for (unsigned int vo = 0; vo < tx.vout.size(); vo++) { + const CTxOut& txout = tx.vout[vo]; + Local out = NanNew(); + out->Set(NanNew("value"), NanNew(txout.nValue)); + out->Set(NanNew("n"), NanNew((boost::int64_t)vo)); + + Local o = NanNew(); + { + const CScript& scriptPubKey = txout.scriptPubKey; + Local out = o; + bool fIncludeHex = true; + + txnouttype type; + vector addresses; + int nRequired; + out->Set(NanNew("asm"), NanNew(scriptPubKey.ToString())); + if (fIncludeHex) { + out->Set(NanNew("hex"), NanNew(HexStr(scriptPubKey.begin(), scriptPubKey.end()))); + } + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { + out->Set(NanNew("type"), NanNew(GetTxnOutputType(type))); + } else { + out->Set(NanNew("reqSigs"), NanNew(nRequired)); + out->Set(NanNew("type"), NanNew(GetTxnOutputType(type))); + Local a = NanNew(); + int ai = 0; + BOOST_FOREACH(const CTxDestination& addr, addresses) { + a->Set(ai, NanNew(CBitcoinAddress(addr).ToString())); + ai++; + } + out->Set(NanNew("addresses"), a); + } + } + out->Set(NanNew("scriptPubKey"), o); + + vout->Set(vo, out); + } + entry->Set(NanNew("vout"), vout); + + if (hashBlock != 0) { + entry->Set(NanNew("blockhash"), NanNew(hashBlock.GetHex())); + map::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) { + CBlockIndex* pindex = (*mi).second; + if (chainActive.Contains(pindex)) { + entry->Set(NanNew("confirmations"), + NanNew(1 + chainActive.Height() - pindex->nHeight)); + entry->Set(NanNew("time"), NanNew((boost::int64_t)pindex->nTime)); + entry->Set(NanNew("blocktime"), NanNew((boost::int64_t)pindex->nTime)); + } else { + entry->Set(NanNew("confirmations"), NanNew(0)); + } + } + } + + + + CTransaction rawTx; + + BOOST_FOREACH(const Value& input, inputs) { + const Object& o = input.get_obj(); + + uint256 txid = ParseHashO(o, "txid"); + + const Value& vout_v = find_value(o, "vout"); + if (vout_v.type() != int_type) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); + int nOutput = vout_v.get_int(); + if (nOutput < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); + + CTxIn in(COutPoint(txid, nOutput)); + rawTx.vin.push_back(in); + } + + set setAddress; + BOOST_FOREACH(const Pair& s, sendTo) { + CBitcoinAddress address(s.name_); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+s.name_); + + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+s.name_); + setAddress.insert(address); + + CScript scriptPubKey; + scriptPubKey.SetDestination(address.Get()); + int64_t nAmount = AmountFromValue(s.value_); + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } + + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << rawTx; + return HexStr(ss.begin(), ss.end()); +} +#endif + /** * Init */ @@ -1154,6 +1452,7 @@ init(Handle target) { NODE_SET_METHOD(target, "getTx", GetTx); NODE_SET_METHOD(target, "pollBlocks", PollBlocks); NODE_SET_METHOD(target, "pollMempool", PollMempool); + NODE_SET_METHOD(target, "broadcastTx", BroadcastTx); } NODE_MODULE(bitcoindjs, init)