start implementing broadcastTx.
This commit is contained in:
parent
9e49864413
commit
b934088f7a
196
lib/bitcoind.js
196
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
|
||||
*/
|
||||
|
||||
@ -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<Object> entry);
|
||||
|
||||
@ -255,6 +262,16 @@ struct async_poll_mempool_data {
|
||||
Persistent<Function> callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* async_broadcast_tx
|
||||
*/
|
||||
|
||||
struct async_broadcast_tx {
|
||||
std::string err_msg;
|
||||
boost::string tx_hex;
|
||||
bool override_fees;
|
||||
Persistent<Function> 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<Object> js_tx = Local<Object>::Cast(args[0]);
|
||||
Local<Function> callback = Local<Function>::Cast(args[2]);
|
||||
|
||||
String::Utf8Value tx_hex_(js_tx->Get(NanNew<String>("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<Function>::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<async_poll_blocks_data*>(req->data);
|
||||
|
||||
// parse hex string from parameter
|
||||
// vector<unsigned char> 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<async_poll_blocks_data*>(req->data);
|
||||
|
||||
if (!data->err_msg.empty()) {
|
||||
Local<Value> err = Exception::Error(String::New(data->err_msg.c_str()));
|
||||
const unsigned argc = 1;
|
||||
Local<Value> 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<Value> argv[argc] = {
|
||||
Local<Value>::New(Null()),
|
||||
Local<Value>::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<Object> 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<Object> entry, const CTransaction& tx, uint256 hashBlock) {
|
||||
String::Utf8Value tx_hex_(entry->Get(NanNew<String>("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<Object> entry, const CTransaction& tx, uint256 hashBlock) {
|
||||
// entry->Set(NanNew<String>("hex"), NanNew<String>(strHex));
|
||||
entry->Set(NanNew<String>("txid"), NanNew<String>(tx.GetHash().GetHex()));
|
||||
entry->Set(NanNew<String>("version"), NanNew<Number>(tx.nVersion));
|
||||
entry->Set(NanNew<String>("locktime"), NanNew<Number>(tx.nLockTime));
|
||||
|
||||
Local<Array> vin = NanNew<Array>();
|
||||
int vi = 0;
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
|
||||
Local<Object> in = NanNew<Object>();
|
||||
if (tx.IsCoinBase()) {
|
||||
in->Set(NanNew<String>("coinbase"), NanNew<String>(HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
|
||||
} else {
|
||||
in->Set(NanNew<String>("txid"), NanNew<String>(txin.prevout.hash.GetHex()));
|
||||
in->Set(NanNew<String>("vout"), NanNew<Number>((boost::int64_t)txin.prevout.n));
|
||||
Local<Object> o = NanNew<Object>();
|
||||
o->Set(NanNew<String>("asm"), NanNew<String>(txin.scriptSig.ToString()));
|
||||
o->Set(NanNew<String>("hex"), NanNew<String>(HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
|
||||
in->Set(NanNew<String>("scriptSig"), o);
|
||||
}
|
||||
in->Set(NanNew<String>("sequence"), NanNew<Number>((boost::int64_t)txin.nSequence));
|
||||
vin->Set(vi, in);
|
||||
vi++;
|
||||
}
|
||||
entry->Set(NanNew<String>("vin"), vin);
|
||||
|
||||
Local<Array> vout = NanNew<Array>();
|
||||
for (unsigned int vo = 0; vo < tx.vout.size(); vo++) {
|
||||
const CTxOut& txout = tx.vout[vo];
|
||||
Local<Object> out = NanNew<Object>();
|
||||
out->Set(NanNew<String>("value"), NanNew<Number>(txout.nValue));
|
||||
out->Set(NanNew<String>("n"), NanNew<Number>((boost::int64_t)vo));
|
||||
|
||||
Local<Object> o = NanNew<Object>();
|
||||
{
|
||||
const CScript& scriptPubKey = txout.scriptPubKey;
|
||||
Local<Object> out = o;
|
||||
bool fIncludeHex = true;
|
||||
|
||||
txnouttype type;
|
||||
vector<CTxDestination> addresses;
|
||||
int nRequired;
|
||||
out->Set(NanNew<String>("asm"), NanNew<String>(scriptPubKey.ToString()));
|
||||
if (fIncludeHex) {
|
||||
out->Set(NanNew<String>("hex"), NanNew<String>(HexStr(scriptPubKey.begin(), scriptPubKey.end())));
|
||||
}
|
||||
if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) {
|
||||
out->Set(NanNew<String>("type"), NanNew<String>(GetTxnOutputType(type)));
|
||||
} else {
|
||||
out->Set(NanNew<String>("reqSigs"), NanNew<Number>(nRequired));
|
||||
out->Set(NanNew<String>("type"), NanNew<String>(GetTxnOutputType(type)));
|
||||
Local<Array> a = NanNew<Array>();
|
||||
int ai = 0;
|
||||
BOOST_FOREACH(const CTxDestination& addr, addresses) {
|
||||
a->Set(ai, NanNew<String>(CBitcoinAddress(addr).ToString()));
|
||||
ai++;
|
||||
}
|
||||
out->Set(NanNew<String>("addresses"), a);
|
||||
}
|
||||
}
|
||||
out->Set(NanNew<String>("scriptPubKey"), o);
|
||||
|
||||
vout->Set(vo, out);
|
||||
}
|
||||
entry->Set(NanNew<String>("vout"), vout);
|
||||
|
||||
if (hashBlock != 0) {
|
||||
entry->Set(NanNew<String>("blockhash"), NanNew<String>(hashBlock.GetHex()));
|
||||
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
|
||||
if (mi != mapBlockIndex.end() && (*mi).second) {
|
||||
CBlockIndex* pindex = (*mi).second;
|
||||
if (chainActive.Contains(pindex)) {
|
||||
entry->Set(NanNew<String>("confirmations"),
|
||||
NanNew<Number>(1 + chainActive.Height() - pindex->nHeight));
|
||||
entry->Set(NanNew<String>("time"), NanNew<Number>((boost::int64_t)pindex->nTime));
|
||||
entry->Set(NanNew<String>("blocktime"), NanNew<Number>((boost::int64_t)pindex->nTime));
|
||||
} else {
|
||||
entry->Set(NanNew<String>("confirmations"), NanNew<Number>(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<CBitcoinAddress> 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<Object> 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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user