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/integration/regtest.js b/integration/regtest.js index bc313a3b..d71010b4 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.01; + 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); }; diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index 82a035c8..c569d54b 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -1037,6 +1037,65 @@ 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; + + // 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"); + } + return NanThrowError(state.GetRejectReason().c_str()); + } + } + } else if (fHaveChain) { + return NanThrowError("transaction already in block chain"); + } + + // Relay the transaction connect peers + RelayTransaction(tx); + + NanReturnValue(Local::New(isolate, NanNew(hashTx.GetHex()))); +} + /** * GetMempoolOutputs * bitcoindjs.getMempoolOutputs() @@ -1174,7 +1233,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); +