From 796d7682f81654a6b617813e9cb775da4c8b59ca Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 20 Jul 2015 14:43:21 -0400 Subject: [PATCH 1/4] Add sendTransaction method to the daemon. --- integration/livenet.js | 1 - src/bitcoindjs.cc | 62 +++++++++++++++++++++++++++++++++++++++++- src/bitcoindjs.h | 3 ++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/integration/livenet.js b/integration/livenet.js index 245b19d5..8ebc0881 100644 --- a/integration/livenet.js +++ b/integration/livenet.js @@ -167,7 +167,6 @@ describe('Basic Functionality', function() { var expected = tx.toBuffer().toString('hex'); txBuffer.toString('hex').should.equal(expected); }); - }); it('get outputs by address', function() { diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index 82a035c8..2dd696ae 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -1037,6 +1037,66 @@ NAN_METHOD(GetInfo) { NanReturnValue(obj); } +/** + * Send Transaction + * bitcoindjs.sendTransaction() + * Will add a transaction to the mempool and broadcast to connected peers. + * @param {string} - The serialized hex string of the transaction. + * @param {boolean} - Skip absurdly high fee checks + */ +NAN_METHOD(SendTransaction) { + Isolate* isolate = Isolate::GetCurrent(); + HandleScope scope(isolate); + + LOCK(cs_main); + + // Decode the transaction + v8::String::Utf8Value param1(args[0]->ToString()); + std::string *input = new std::string(*param1); + CTransaction tx; + if (!DecodeHexTx(tx, *input)) { + return NanThrowError("TX decode failed"); + } + uint256 hashTx = tx.GetHash(); + + // Skip absurdly high fee check + bool allowAbsurdFees = false; + if (args.Length() > 1) { + allowAbsurdFees = args[1]->BooleanValue(); + } + + CCoinsViewCache &view = *pcoinsTip; + const CCoins* existingCoins = view.AccessCoins(hashTx); + bool fHaveMempool = mempool.exists(hashTx); + bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000; + if (!fHaveMempool && !fHaveChain) { + CValidationState state; + bool fMissingInputs; + char *errorMessage; + + // Attempt to add the transaction to the mempool + if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, !allowAbsurdFees)) { + if (state.IsInvalid()) { + sprintf(errorMessage, "%i: %s", state.GetRejectCode(), state.GetRejectReason().c_str()); + return NanThrowError(errorMessage); + } else { + if (fMissingInputs) { + return NanThrowError("Missing inputs"); + } + sprintf(errorMessage, "%s", state.GetRejectReason()); + return NanThrowError(errorMessage); + } + } + } else if (fHaveChain) { + return NanThrowError("transaction already in block chain"); + } + + // Relay the transaction connect peers + RelayTransaction(tx); + + return NanReturnValue(NanNew(hashTx.GetHex())); +} + /** * GetMempoolOutputs * bitcoindjs.getMempoolOutputs() @@ -1174,7 +1234,7 @@ init(Handle target) { NODE_SET_METHOD(target, "getMempoolOutputs", GetMempoolOutputs); NODE_SET_METHOD(target, "addMempoolUncheckedTransaction", AddMempoolUncheckedTransaction); NODE_SET_METHOD(target, "verifyScript", VerifyScript); - + NODE_SET_METHOD(target, "sendTransaction", SendTransaction); } NODE_MODULE(bitcoindjs, init) diff --git a/src/bitcoindjs.h b/src/bitcoindjs.h index 40d1a803..050d6830 100644 --- a/src/bitcoindjs.h +++ b/src/bitcoindjs.h @@ -12,6 +12,7 @@ #include "scheduler.h" #include "core_io.h" #include "script/bitcoinconsensus.h" +#include "consensus/validation.h" NAN_METHOD(StartBitcoind); NAN_METHOD(OnBlocksReady); @@ -26,3 +27,5 @@ NAN_METHOD(GetChainWork); NAN_METHOD(GetMempoolOutputs); NAN_METHOD(AddMempoolUncheckedTransaction); NAN_METHOD(VerifyScript); +NAN_METHOD(SendTransaction); + From 2312e53038cdcb99142c57f5e076882cbcbbd0dd Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 20 Jul 2015 15:08:34 -0400 Subject: [PATCH 2/4] Fix compilation of bindings for SendTransaction. --- src/bitcoindjs.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index 2dd696ae..c569d54b 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -1072,19 +1072,18 @@ NAN_METHOD(SendTransaction) { if (!fHaveMempool && !fHaveChain) { CValidationState state; bool fMissingInputs; - char *errorMessage; // Attempt to add the transaction to the mempool if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, !allowAbsurdFees)) { if (state.IsInvalid()) { + char *errorMessage; sprintf(errorMessage, "%i: %s", state.GetRejectCode(), state.GetRejectReason().c_str()); return NanThrowError(errorMessage); } else { if (fMissingInputs) { return NanThrowError("Missing inputs"); } - sprintf(errorMessage, "%s", state.GetRejectReason()); - return NanThrowError(errorMessage); + return NanThrowError(state.GetRejectReason().c_str()); } } } else if (fHaveChain) { @@ -1094,7 +1093,7 @@ NAN_METHOD(SendTransaction) { // Relay the transaction connect peers RelayTransaction(tx); - return NanReturnValue(NanNew(hashTx.GetHex())); + NanReturnValue(Local::New(isolate, NanNew(hashTx.GetHex()))); } /** From b53fa3f6bb03e31e7e4b2c85d8fdb1652f4d79b6 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 22 Jul 2015 13:59:28 -0400 Subject: [PATCH 3/4] Added regtest for sendTransaction method. --- integration/regtest.js | 104 ++++++++++++++++++++++++++++++++++++++--- lib/daemon.js | 4 ++ 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/integration/regtest.js b/integration/regtest.js index bc313a3b..19fe67fa 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -21,12 +21,31 @@ var assert = chai.assert; var sinon = require('sinon'); var BitcoinRPC = require('bitcoind-rpc'); var blockHashes = []; +var unspentTransactions = []; +var coinbasePrivateKey; +var privateKey = bitcore.PrivateKey(); +var destKey = bitcore.PrivateKey(); -describe('Basic Functionality', function() { +describe('Daemon Binding Functionality', function() { before(function(done) { this.timeout(30000); + // Add the regtest network + bitcore.Networks.remove(bitcore.Networks.testnet); + bitcore.Networks.add({ + name: 'regtest', + alias: 'regtest', + pubkeyhash: 0x6f, + privatekey: 0xef, + scripthash: 0xc4, + xpubkey: 0x043587cf, + xprivkey: 0x04358394, + networkMagic: 0xfabfb5da, + port: 18444, + dnsSeeds: [ ] + }); + var datadir = __dirname + '/data'; rimraf(datadir + '/regtest', function(err) { @@ -62,18 +81,63 @@ describe('Basic Functionality', function() { console.log('Generating 100 blocks...'); - client.generate(100, function(err, response) { + // Generate enough blocks so that the initial coinbase transactions + // can be spent. + + client.generate(150, function(err, response) { if (err) { throw err; } blockHashes = response.result; - done(); + + // The original coinbase transactions when using regtest spend to + // a non-standard pubkeyout instead of a pubkeyhashout. + // We'll construct a new transaction that will send funds + // to a new address with pubkeyhashout for later testing. + + console.log('Preparing unspent outputs...'); + + client.getBalance(function(err, response) { + if (err) { + throw err; + } + + var amount = response.result; + var fee = 0.0001; + var network = bitcore.Networks.get('regtest'); + var address = privateKey.toAddress(network); + + client.sendToAddress(address, amount - fee, '', '', function(err, response) { + if (err) { + throw err; + } + + var txid = response.result; + client.getTransaction(txid, function(err, response) { + if (err) { + throw err; + } + + var tx = bitcore.Transaction(); + tx.fromString(response.result.hex); + unspentTransactions.push(tx); + + // Include this transaction in a block so that it can + // be spent in tests + client.generate(1, function(err, response) { + if (err) { + throw err; + } + + console.log('Testing setup complete!'); + done(); + }); + }); + }); + }); }); - }); - }); - }); after(function(done) { @@ -169,4 +233,32 @@ describe('Basic Functionality', function() { }); }); + describe('send transaction functionality', function() { + + it('will not error and return the transaction hash', function() { + + var unspentTx = unspentTransactions.shift(); + + var utxo = { + txid: unspentTx.hash, + outputIndex: 1, + script: unspentTx.outputs[1].script, + satoshis: unspentTx.outputs[1].satoshis + }; + + // create and sign the transaction + var tx = bitcore.Transaction(); + tx.from(utxo); + tx.change(privateKey.toAddress()); + tx.to(destKey.toAddress(), 100000); + tx.sign(privateKey); + + // test sending the transaction + var hash = bitcoind.sendTransaction(tx.serialize()); + hash.should.equal(tx.hash); + + }); + + }); + }); diff --git a/lib/daemon.js b/lib/daemon.js index 4e529ebc..e5487e40 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -344,6 +344,10 @@ Daemon.prototype.getChainWork = function(blockHash) { return bitcoindjs.getChainWork(blockHash); }; +Daemon.prototype.sendTransaction = function(transaction, allowAbsurdFees) { + return bitcoindjs.sendTransaction(transaction, allowAbsurdFees); +}; + Daemon.prototype.getTransaction = function(txid, queryMempool, callback) { return bitcoindjs.getTransaction(txid, queryMempool, callback); }; From eab878cec1b3c6678689daab9473dbddab99a328 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 22 Jul 2015 14:29:43 -0400 Subject: [PATCH 4/4] Include a higher fee. --- integration/regtest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/regtest.js b/integration/regtest.js index 19fe67fa..d71010b4 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -103,7 +103,7 @@ describe('Daemon Binding Functionality', function() { } var amount = response.result; - var fee = 0.0001; + var fee = 0.01; var network = bitcore.Networks.get('regtest'); var address = privateKey.toAddress(network);