diff --git a/README.md b/README.md index 5bfec5ac..b0f0de94 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,26 @@ # BCoin -**BCoin** is a bitcoin node which can act as an SPV node or a fully validating -fullnode. Bcoin runs in node.js, but it can also be browserified. +**BCoin** is a bitcoin library which can also act as an SPV node or a full +node. It is consensus aware and is up to date with the latest BIPs: it supports +segregated witness, versionbits, and CSV. It also has preliminary support for +bip151 (peer-to-peer encryption), bip152 (compact block relay), and bip114 +(MAST). It runs in node.js, but it can also be browserified. + +Try it in the browser: http://bcoin.io/browser.html ## Features -- SPV mode -- HD Wallets (using BIP44 (or optionally BIP45) derivation) +- HD Wallets (using BIP44 derivation and accounts) - Fully browserifiable - Full block validation - Full block database -- Fully validating mempool (stored in-memory or on-disk) +- Fully validating mempool (stored in-memory or optionally on-disk) - Wallet database - HTTP server which acts as a wallet server and can also serve: blocks, txs (by hash/address), and utxos (by id/address). - - Fast UTXO retrieval by address for wallets (10000 utxos from 10000 different - addresses in ~700ms, 50000+ utxos from 10-100 addresses in ~400ms) -- Segregated witness support for block/tx validation and wallets. -- Versionbits suport. +- Full segregated witness support for block/tx validation and wallets. +- Versionbits, CSV, BIP151, BIP152, MAST support. +- SPV mode ## Install @@ -41,16 +44,30 @@ Read the docs here: http://bcoin.io/docs/ ### Creating a blockchain and mempool ``` js -var bcoin = require('bcoin').set('regtest'); +var bcoin = require('bcoin'); + +bcoin.set({ + // Default network (so we can avoid passing + // the `network` option into every object below. + network: 'regtest', + // Enable the global worker pool + // for mining and transaction verification. + useWorkers: true +}); + +// Start up a blockchain, mempool, and miner using in-memory +// databases (stored in a red-black tree instead of on-disk). var chain = new bcoin.chain({ db: 'memory' }); var mempool = new bcoin.mempool({ chain: chain, db: 'memory' }); var miner = new bcoin.miner({ chain: chain, mempool: mempool }); -// Create a block "attempt" +// Open the miner (initialize the databases, etc). +// Miner will implicitly call `open` on chain and mempool. miner.open(function(err) { if (err) throw err; + // Create a block "attempt". miner.createBlock(function(err, attempt) { if (err) throw err; @@ -78,10 +95,17 @@ miner.open(function(err) { ``` js var bcoin = require('bcoin').set('main'); -var chain = new bcoin.chain({ db: 'leveldb' }); +// Create a blockchain and store it in leveldb. +// `db` also accepts `rocksdb` and `lmdb`. +var prefix = process.env.HOME + '/my-bcoin-environment'; +var chain = new bcoin.chain({ db: 'leveldb', location: prefix + '/chain' }); + var mempool = new bcoin.mempool({ chain: chain, db: 'memory' }); + +// Create a network pool of peers with a limit of 8 peers. var pool = new bcoin.pool({ chain: chain, mempool: mempool, size: 8 }); +// Open the pool (implicitly opens mempool and chain). pool.open(function(err) { if (err) throw err; @@ -92,8 +116,9 @@ pool.open(function(err) { // Start the blockchain sync. pool.startSync(); + // Watch the action chain.on('block', function(block) { - console.log('Added block:'); + console.log('Connected block to blockchain:'); console.log(block); }); @@ -104,12 +129,12 @@ pool.open(function(err) { pool.on('tx', function(tx) { console.log('Saw transaction:'); - console.log(tx); + console.log(tx.rhash); }); }); -// Start up a segnet4 sync while -// we're at it (because we can). +// Start up a segnet4 sync in-memory +// while we're at it (because we can). var tchain = new bcoin.chain({ network: 'segnet4', @@ -162,10 +187,10 @@ tpool.open(function(err) { ``` js var bcoin = require('bcoin').set('testnet'); +// SPV chains only store the chain headers. var chain = new bcoin.chain({ db: 'leveldb', - // A custom chaindb location: - location: process.env.HOME + '/chain.db', + location: process.env.HOME + '/spvchain', spv: true }); @@ -189,7 +214,7 @@ pool.open(function(err) { if (err) throw err; - console.log('Created wallet with address %s', wallet.getAddress()); + console.log('Created wallet with address %s', wallet.getAddress('base58')); // Add our address to the spv filter. pool.watchAddress(wallet.getAddress()); @@ -223,7 +248,15 @@ var node = bcoin.fullnode({ useCheckpoints: true, debug: true, // Primary wallet passphrase - passsphrase: 'node' + passsphrase: 'node', + logLevel: 'info' +}); + +// We get a lot of errors sometimes, +// usually from peers hanging up on us. +// Just ignore them for now. +node.on('error', function(err) { + ; }); // Start the node @@ -243,25 +276,28 @@ node.open(function(err) { if (err) throw err; - console.log('Created wallet with address: %s', wallet.getAddress()); + console.log('Created wallet with address: %s', wallet.getAddress('base58')); // Start syncing the blockchain - // (this will take a while since we're a fullnode) node.startSync(); // Wait for balance and send it to a new address. wallet.once('balance', function(balance) { // Create a transaction, fill // it with coins, and sign it. - var to = { - address: newReceiving, - value: balance.total + var options = { + subtractFee: true, + outputs: [{ + address: newReceiving, + value: balance.total + }] }; - wallet.createTX({ subtractFee: true }, [to], function(err, tx) { + wallet.createTX(options, function(err, tx) { if (err) throw err; - wallet.sign(tx, function(err) { + // Need to pass our passphrase back in to sign! + wallet.sign(tx, 'foo', function(err) { if (err) throw err; @@ -283,15 +319,15 @@ node.open(function(err) { }); node.chain.on('block', function(block) { - console.log(block); + ; }); node.mempool.on('tx', function(tx) { - console.log(block); + ; }); node.chain.on('full', function() { - node.mempool.getAll(function(err, txs) { + node.mempool.getHistory(function(err, txs) { if (err) throw err; @@ -305,11 +341,16 @@ node.chain.on('full', function() { ``` bash $ cd ~/bcoin $ make # Browserify bcoin -$ node browser/server.js 80 # Start up a simple webserver and websocket->tcp bridge +$ node browser/server.js 8080 # Start up a simple webserver and websocket->tcp bridge +$ chromium http://localhost:8080 ``` You should see something like this: http://i.imgur.com/0pWySyZ.png +This is a simple proof-of-concept. It's not a pretty interface. I hope to see +others doing something far more interesting. A browser extension may be better: +the chrome extension API exposes raw TCP access. + ### CLI Usage ``` bash @@ -319,36 +360,316 @@ $ node bin/bcoin-cli block 0 # View primary wallet $ node bin/bcoin-cli wallet primary # Send a tx -$ node bin/bcoin-cli send [address] 0.01 +$ node bin/bcoin-cli send primary [address] 0.01 +# View balance +$ node bin/bcoin-cli balance primary # View the mempool $ node bin/bcoin-cli mempool ``` -### TX creation +## TX creation + +Normal transactions in bcoin are immutable. The primary TX object contains a +bunch of consensus and policy checking methods. A lot of it is for internal use +and pretty boring for users of this library. + +BCoin also offers a mutable transaction object (MTX). Mutable transactions +inherit from the TX object, but can also be signed and modified. + +``` js +var bcoin = require('bcoin'); +var assert = require('assert'); +var constants = bcoin.protocol.constants; + +// Create an HD master keypair with a mnemonic. +var master = bcoin.hd.fromMnemonic(); + +// Derive another private hd key (we don't want to use our master key!). +var key = master.derive('m/44/0/0/0/0'); + +// Create a "keyring" object. A keyring object is basically a key manager that +// is also able to tell you info such as: your redeem script, your scripthash, +// your program hash, your pubkey hash, your scripthash program hash, etc. +// In this case, we'll make it simple and just add one key for a +// pubkeyhash address. `getPublicKey` returns the non-hd public key. +var keyring = new bcoin.keyring({ key: key.getPublicKey() }); + +console.log(keyring.getAddress()); + +// Create a fake coinbase for our funding. +var cb = new bcoin.mtx(); + +// Add a typical coinbase input +cb.addInput({ + prevout: { + hash: constants.NULL_HASH, + index: 0 + }, + script: new bcoin.script(), + sequence: 0xffffffff +}); + +// Send 50,000 satoshis to ourself. +cb.addOutput({ + address: keyring.getAddress(), + value: 50000 +}); + +// Create our redeeming transaction. +var tx = new bcoin.mtx(); + +// Add output 0 from our coinbase. +tx.addInput(cb, 0); + +// Send 10,000 satoshis to ourself, +// creating a fee of 40,000 satoshis. +tx.addOutput({ + address: keyring.getAddress(), + value: 10000 +}); + +// Sign input 0: pass in our keyring and private key. +tx.sign(0, keyring, key); + +// Commit our transaction and make it immutable. +// This turns it from an MTX into a TX object. +tx = tx.toTX(); + +// The transaction should now verify. +assert(tx.verify()); +assert(tx.getFee() === 40000); +``` + +### Coin Selection + +The above method works, but is pretty contrived. In reality, you probably +wouldn't select inputs and calculate the fee by hand. You would want a +change output added. BCoin has a nice method of dealing with this. + +Let's try it more realistically: + +``` js +var bcoin = require('bcoin'); +var assert = require('assert'); +var constants = bcoin.protocol.constants; + +var master = bcoin.hd.fromMnemonic(); +var key = master.derive('m/44/0/0/0/0'); +var keyring = new bcoin.keyring({ key: key.getPublicKey() }); +var cb = new bcoin.mtx(); + +cb.addInput({ + prevout: { + hash: constants.NULL_HASH, + index: 0 + }, + script: new bcoin.script(), + sequence: 0xffffffff +}); + +// Send 50,000 satoshis to ourselves. +cb.addOutput({ + address: keyring.getAddress(), + value: 50000 +}); + +// Our available coins. +var coins = []; + +// Convert the coinbase output to a Coin +// object and add it to our available coins. +// In reality you might get these coins from a wallet. +var coin = bcoin.coin.fromTX(cb, 0); +coins.push(coin); + +// Create our redeeming transaction. +var tx = new bcoin.mtx(); + +// Send 10,000 satoshis to ourself. +tx.addOutput({ + address: keyring.getAddress(), + value: 10000 +}); + +// Now that we've created the output, we can do some coin selection (the output +// must be added first so we know how much money is needed and also so we can +// accurately estimate the size for fee calculation). + +// Select coins from our array and add inputs. +// Calculate fee and add a change output. +tx.fill(coins, { + // Use a rate of 10,000 satoshis per kb. + // With the `fullnode` object, you can + // use the fee estimator for this instead + // of blindly guessing. + rate: 10000, + // Send the change back to ourselves. + changeAddress: keyring.getAddress() +}); + +// Sign input 0 +tx.sign(0, keyring, key); + +// Commit our transaction and make it immutable. +// This turns it from an MTX into a TX. +tx = tx.toTX(); + +// The transaction should now verify. +assert(tx.verify()); +``` + +## Scripting + +Scripts are array-like objects with some helper functions. + +``` js +var bcoin = require('bcoin'); +var assert = require('assert'); +var bn = bcoin.bn; +var opcodes = bcoin.script.opcodes; + +var output = new bcoin.script(); +output.push(opcodes.OP_DROP); +output.push(opcodes.OP_ADD); +output.push(new bn(7)); +output.push(opcodes.OP_NUMEQUAL); +// Compile the script to its binary representation +// (you must do this if you change something!). +output.compile(); +assert(output.getSmall(2) === 7); // compiled as OP_7 + +var input = new bcoin.script(); +input.set(0, 'hello world'); // add some metadata +input.push(new bn(2)); +input.push(new bn(5)); +input.push(input.shift()); +assert(input.getString(2) === 'hello world'); +input.compile(); + +// A stack is another array-like object which contains +// only Buffers (whereas scripts contain Opcode objects). +var stack = new bcoin.stack(); +input.execute(stack); +output.execute(stack); +// Verify the script was successful in its execution: +assert(stack.length === 1); +assert(bcoin.script.bool(stack.pop()) === true); +``` + +Using a witness would be similar, but witnesses do not get executed, they +simply _become_ the stack. The witness object itself is very similar to the +Stack object (an array-like object containing Buffers). + +``` js +var witness = new bcoin.witness(); +witness.push(new bn(2)); +witness.push(new bn(5)); +witness.push('hello world'); + +var stack = witness.toStack(); +output.execute(stack); +``` + +## Wallet usage + +BCoin maintains a wallet database which contains every wallet. Wallets are _not +usable_ without also using a wallet database. For testing, the wallet database +can be in-memory, but it must be there. + +Wallets in bcoin use bip44. They also originally supported bip45 for multisig, +but support was removed to reduce code complexity, and also because bip45 +doesn't seem to add any benefit in practice. + +The wallet database can contain many different wallets, with many different +accounts, with many different addresses for each account. BCoin should +theoretically be able to scale to hundreds of thousands of +wallets/accounts/addresses. + +Each account can be of a different type. You could have a pubkeyhash account, +as well as a multisig account, a witness pubkeyhash account, etc. + +Note that accounts should not be accessed directly from the public API. They do +not have locks which can lead to race conditions during writes. TODO -### Scripting +## HTTP API & Websocket Events TODO -### Wallet usage +## Design -TODO +BCoin is thoroughly event driven. It has a fullnode object, but BCoin was +specifically designed so the mempool, blockchain, p2p pool, and wallet database +could all be used separately. All the fullnode object does is tie these things +together. It's essentially a huge proxying of events. The general communication +between these things looks something like this: -### Accessing the mempool +``` +pool -> block event -> chain +pool -> tx event -> mempool +chain -> block event -> mempool/miner +chain -> tx event -> walletdb +chain -> reorg event -> walletdb/mempool/miner +mempool -> tx event -> walletdb/miner +miner -> block event -> chain +walletdb -> tx event -> websocket server +websocket server -> tx event -> websocket client +http client -> tx -> http server -> mempool +``` -TODO +Not only does the loose coupling make testing easier, it ensures people can +utilize bcoin for many use cases. -### HTTP server/client +### Performance -TODO +Non-javscript people reading this may think using javascript isn't a wise +descision. + +#### Javascript + +Javascript is inherently slow due to how dynamic it is, but modern JITs have +solved this issue using very clever optimization and dynamic recompilation +techniques. v8 in some cases can [rival the speed of C++][v8] if the code is +well-written. + +#### Concurrency + +BCoin runs in node.js, so the javascript code is limited to one thread. We +solve this limitation by spinning up persistent worker processes for +transaction verification (webworkers when in the browser). This ensures the +blockchain and mempool do not block the master process very much. It also means +transaction verification can be parallelized. + +Strangely enough, workers are faster in the browser than they are in node since +you are allowed to share memory between threads using the transferrable api +(Uint8Arrays can be "transferred" to another thread). In node, you have to pipe +data to another process. + +But of course, there is a benefit to having a multi-process architecture: the +worker processes can die on their own without disturbing the master process. + +BCoin uses [secp256k1-node][secp256k1-node] for ecdsa verification, which is a +node.js binding to Pieter Wuille's blazingly fast [libsecp256k1][libsecp256k1] +library. + +In the browser, bcoin will use [elliptic][elliptic], the fastest javascript +ecdsa implementation. It will obviously never beat C and hand-optimized +assembly, but it's still usable. + +#### Benefits + +The real feature of javascript is that your code will run almost anywhere. With +bcoin, we now have a full node that will run on almost any browser, on laptops, +on servers, on smartphones, on most devices you can imagine, even by simply +visting a webpage. ## LICENSE This software is licensed under the MIT License. Copyright Fedor Indutny, 2014-2016. +Copyright Christopher Jeffrey, 2014-2016. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -369,5 +690,7 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -[bip37]: https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki -[escrow]: https://en.bitcoin.it/wiki/Contract#Example_2:_Escrow_and_dispute_mediation +[v8]: https://www.youtube.com/watch?v=UJPdhx5zTaw +[libsecp256k1]: https://github.com/bitcoin-core/secp256k1 +[secp256k1-node]: https://github.com/cryptocoinjs/secp256k1-node +[elliptic]: https://github.com/indutny/elliptic diff --git a/bin/bcoin-cli b/bin/bcoin-cli index e1c4d17c..268418bb 100755 --- a/bin/bcoin-cli +++ b/bin/bcoin-cli @@ -58,7 +58,7 @@ function createWallet(callback) { client.createWallet(options, function(err, wallet) { if (err) return callback(err); - utils.print(wallet); + utils.log(wallet); callback(); }); } @@ -73,7 +73,7 @@ function addKey(callback) { client.addKey(id, argv.account, keys, function(err, wallet) { if (err) return callback(err); - utils.print('added'); + utils.log('added'); callback(); }); } @@ -84,7 +84,7 @@ function createAccount(callback) { client.createWalletAccount(id, account, function(err, account) { if (err) return callback(err); - utils.print(account); + utils.log(account); callback(); }); } @@ -94,7 +94,7 @@ function getAccounts(callback) { client.getWalletAccounts(id, function(err, accounts) { if (err) return callback(err); - utils.print(accounts); + utils.log(accounts); callback(); }); } @@ -109,7 +109,7 @@ function removeKey(callback) { client.removeKey(id, argv.account, keys, function(err) { if (err) return callback(err); - utils.print('removed'); + utils.log('removed'); callback(); }); } @@ -120,7 +120,7 @@ function getWallet(callback) { client.getWallet(id, argv.account, passphrase, function(err, wallet) { if (err) return callback(err); - utils.print(wallet); + utils.log(wallet); wallet.destroy(); callback(); }); @@ -132,7 +132,7 @@ function getTX(callback) { return client.getTXByAddress(hash, function(err, txs) { if (err) return callback(err); - utils.print(txs); + utils.log(txs); callback(); }); } @@ -142,11 +142,11 @@ function getTX(callback) { return callback(err); if (!tx) { - utils.print('TX not found.'); + utils.log('TX not found.'); return callback(); } - utils.print(tx); + utils.log(tx); callback(); }); } @@ -162,13 +162,13 @@ function getBlock(callback) { return callback(err); if (!block) { - utils.print('Block not found.'); + utils.log('Block not found.'); return callback(); } - utils.print(block); - utils.print('Coinbase Data:'); - utils.print(block.txs[0].inputs[0].script.getCoinbaseFlags()); + utils.log(block); + utils.log('Coinbase Data:'); + utils.log(block.txs[0].inputs[0].script.getCoinbaseFlags()); callback(); }); } @@ -180,7 +180,7 @@ function getCoin(callback) { return client.getCoinsByAddress(hash, function(err, coins) { if (err) return callback(err); - utils.print(coins); + utils.log(coins); callback(); }); } @@ -190,11 +190,11 @@ function getCoin(callback) { return callback(err); if (!coin) { - utils.print('Coin not found.'); + utils.log('Coin not found.'); return callback(); } - utils.print(coin); + utils.log(coin); callback(); }); } @@ -204,7 +204,7 @@ function getWalletHistory(callback) { client.getWalletHistory(id, argv.account, function(err, txs) { if (err) return callback(err); - utils.print(txs); + utils.log(txs); callback(); }); } @@ -213,29 +213,29 @@ function listenWallet(callback) { var id = getID(); client.join(id); client.on('tx', function(tx, map) { - utils.print('TX:'); - utils.print(tx); - utils.print(map); + utils.log('TX:'); + utils.log(tx); + utils.log(map); }); client.on('updated', function(tx, map) { - utils.print('TX updated:'); - utils.print(tx); - utils.print(map); + utils.log('TX updated:'); + utils.log(tx); + utils.log(map); }); client.on('confirmed', function(tx, map) { - utils.print('TX updated:'); - utils.print(tx); - utils.print(map); + utils.log('TX updated:'); + utils.log(tx); + utils.log(map); }); client.on('address', function(receive, change) { - utils.print('New addresses allocated:'); - utils.print(receive); - utils.print(change); + utils.log('New addresses allocated:'); + utils.log(receive); + utils.log(change); }); client.on('balance', function(tx, map) { - utils.print('Balance:'); - utils.print(tx); - utils.print(map); + utils.log('Balance:'); + utils.log(tx); + utils.log(map); }); } @@ -244,9 +244,9 @@ function getBalance(callback) { client.getWalletBalance(id, argv.account, function(err, balance) { if (err) return callback(err); - utils.print('Confirmed: %s', utils.btc(balance.confirmed)); - utils.print('Unconfirmed: %s', utils.btc(balance.unconfirmed)); - utils.print('Total: %s', utils.btc(balance.total)); + utils.log('Confirmed: %s', utils.btc(balance.confirmed)); + utils.log('Unconfirmed: %s', utils.btc(balance.unconfirmed)); + utils.log('Total: %s', utils.btc(balance.total)); callback(); }); } @@ -255,7 +255,7 @@ function getMempool(callback) { client.getMempool(function(err, txs) { if (err) return callback(err); - utils.print(txs); + utils.log(txs); callback(); }); } @@ -274,8 +274,8 @@ function sendTX(callback) { client.walletSend(id, {outputs:[output]}, function(err, tx) { if (err) return callback(err); - utils.print(tx); - utils.print(tx.toRaw('hex')); + utils.log(tx); + utils.log(tx.toRaw('hex')); callback(); }); } @@ -294,8 +294,8 @@ function createTX(callback) { client.walletCreate(id, options, [output], function(err, tx) { if (err) return callback(err); - utils.print(tx); - utils.print(tx.toRaw('hex')); + utils.log(tx); + utils.log(tx.toRaw('hex')); callback(); }); } @@ -307,8 +307,8 @@ function signTX(callback) { client.walletSign(id, tx, options, function(err, tx) { if (err) return callback(err); - utils.print(tx); - utils.print(tx.toRaw('hex')); + utils.log(tx); + utils.log(tx.toRaw('hex')); callback(); }); } @@ -319,7 +319,7 @@ function zap(callback) { client.walletZap(id, argv.account, age, function(err) { if (err) return callback(err); - utils.print('Zapped!'); + utils.log('Zapped!'); callback(); }); } @@ -329,8 +329,8 @@ function broadcast(callback) { client.broadcast(tx, function(err, tx) { if (err) return callback(err); - utils.print('Broadcasted:'); - utils.print(tx); + utils.log('Broadcasted:'); + utils.log(tx); callback(); }); } @@ -340,7 +340,7 @@ function view(callback) { client.walletFill(tx, function(err, tx) { if (err) return callback(err); - utils.print(tx); + utils.log(tx); callback(); }); } @@ -386,32 +386,32 @@ function main(callback) { case 'block': return getBlock(callback); default: - utils.print('Unrecognized command.'); - utils.print('Commands:'); - utils.print(' $ wallet [id] --keys [hdkeys]' + utils.log('Unrecognized command.'); + utils.log('Commands:'); + utils.log(' $ wallet [id] --keys [hdkeys]' + ' --type [pubkeyhash/multisig] -m [m-value]' + ' -n [n-value] --witness: View or create wallet by ID.'); - utils.print(' $ listen [id]: Listen for wallet events.'); - utils.print(' $ getwallet [id]: View wallet by ID.'); - utils.print(' $ addkey [id] --keys [hdkeys]: Add keys to wallet.'); - utils.print(' $ rmkey [id] --keys [hdkeys]: Remove keys from wallet.'); - utils.print(' $ balance [id]: Get wallet balance.'); - utils.print(' $ history [id]: View wallet TX history.'); - utils.print(' $ accounts [id]: List account names.'); - utils.print(' $ account [id] [acct]: Get account details.'); - utils.print(' $ send [id] [address] [value] --script [code]: Send transaction.'); - utils.print(' $ create [id] [address] [value] --script [code]: Create transaction.'); - utils.print(' $ sign [id] [tx-hex]: Sign transaction.'); - utils.print(' $ zap [id] --age [age]: Zap pending wallet TXs.'); - utils.print(' $ broadcast [tx-hex]: Broadcast transaction.'); - utils.print(' $ view [tx-hex]: View transaction.'); - utils.print(' $ mempool: Get mempool snapshot.'); - utils.print(' $ tx [hash/address]: View transactions.'); - utils.print(' $ coin [hash+index/address]: View coins.'); - utils.print(' $ block [hash/height]: View block.'); - utils.print('Other Options:'); - utils.print(' --passphrase [passphrase]: For signing and account creation.'); - utils.print(' --account [acctname]: Account name.'); + utils.log(' $ listen [id]: Listen for wallet events.'); + utils.log(' $ getwallet [id]: View wallet by ID.'); + utils.log(' $ addkey [id] --keys [hdkeys]: Add keys to wallet.'); + utils.log(' $ rmkey [id] --keys [hdkeys]: Remove keys from wallet.'); + utils.log(' $ balance [id]: Get wallet balance.'); + utils.log(' $ history [id]: View wallet TX history.'); + utils.log(' $ accounts [id]: List account names.'); + utils.log(' $ account [id] [acct]: Get account details.'); + utils.log(' $ send [id] [address] [value] --script [code]: Send transaction.'); + utils.log(' $ create [id] [address] [value] --script [code]: Create transaction.'); + utils.log(' $ sign [id] [tx-hex]: Sign transaction.'); + utils.log(' $ zap [id] --age [age]: Zap pending wallet TXs.'); + utils.log(' $ broadcast [tx-hex]: Broadcast transaction.'); + utils.log(' $ view [tx-hex]: View transaction.'); + utils.log(' $ mempool: Get mempool snapshot.'); + utils.log(' $ tx [hash/address]: View transactions.'); + utils.log(' $ coin [hash+index/address]: View coins.'); + utils.log(' $ block [hash/height]: View block.'); + utils.log('Other Options:'); + utils.log(' --passphrase [passphrase]: For signing and account creation.'); + utils.log(' --account [acctname]: Account name.'); return callback(); } } @@ -476,7 +476,7 @@ client.getInfo(function(err, info) { } if (!argv.args[0]) - utils.print(info); + utils.log(info); main(function(err) { if (err) { diff --git a/bin/node b/bin/node index 5d7c7ca6..457a3200 100755 --- a/bin/node +++ b/bin/node @@ -2,28 +2,29 @@ 'use strict'; -var bcoin = require('../').set({ debug: true, debugFile: true }); +var bcoin = require('../'); var utils = bcoin.utils; var assert = utils.assert; process.on('uncaughtException', function(err) { - bcoin.debug(err.stack); - bcoin.error(err); + node.logger.debug(err.stack); + node.logger.error(err); process.exit(1); }); var node = new bcoin.fullnode({ + logLevel: 'debug', + logFile: true, + db: 'leveldb', prune: process.argv.indexOf('--prune') !== -1, useCheckpoints: process.argv.indexOf('--checkpoints') !== -1, - listen: process.argv.indexOf('--listen') !== -1, selfish: process.argv.indexOf('--selfish') !== -1, headers: process.argv.indexOf('--headers') !== -1, - mine: process.argv.indexOf('--mine') !== -1, parallel: process.argv.indexOf('--parallel') !== -1 }); node.on('error', function(err) { - bcoin.error(err); + ; }); node.open(function(err) { @@ -34,7 +35,7 @@ node.open(function(err) { if (err) throw err; - if (!node.options.mine) { + if (process.argv.indexOf('--mine') === -1) { node.startSync(); return; } diff --git a/bin/spvnode b/bin/spvnode index b5f29ccc..3a5be51e 100755 --- a/bin/spvnode +++ b/bin/spvnode @@ -2,18 +2,20 @@ 'use strict'; -var bcoin = require('../').set({ debug: true, debugFile: true }); +var bcoin = require('../'); var utils = bcoin.utils; var assert = utils.assert; var node = bcoin.spvnode({ - // passphrase: 'node', - preload: process.argv.indexOf('--preload') !== -1, - useCheckpoints: process.argv.indexOf('--checkpoints') !== -1 + logLevel: 'debug', + logFile: true, + db: 'leveldb', + useCheckpoints: process.argv.indexOf('--checkpoints') !== -1, + headers: process.argv.indexOf('--headers') !== -1 }); node.on('error', function(err) { - bcoin.debug(err.stack + ''); + ; }); node.open(function(err) { @@ -23,7 +25,7 @@ node.open(function(err) { if (process.argv.indexOf('--test') !== -1) { node.pool.watchAddress('1VayNert3x1KzbpzMGt2qdqrAThiRovi8'); node.on('tx', function(tx) { - utils.print(tx); + utils.log(tx); }); } diff --git a/browser/proxysocket.js b/browser/proxysocket.js index 63fb47fa..9e408ce9 100644 --- a/browser/proxysocket.js +++ b/browser/proxysocket.js @@ -85,7 +85,7 @@ ProxySocket.prototype.connect = function connect(port, host) { return this.once('info', connect.bind(this, port, host)); if (this.info.pow) { - bcoin.debug( + utils.log( 'Solving proof of work to create socket (%d, %s) -- please wait.', port, host); @@ -102,7 +102,7 @@ ProxySocket.prototype.connect = function connect(port, host) { pow.writeUInt32LE(nonce, 0, true); } while (utils.cmp(utils.dsha256(pow), this.target) >= 0); - bcoin.debug('Solved proof of work: %d', nonce); + utils.log('Solved proof of work: %d', nonce); } this.socket.emit('tcp connect', port, host, nonce); diff --git a/lib/bcoin/address.js b/lib/bcoin/address.js index 3745dd83..c367ccf9 100644 --- a/lib/bcoin/address.js +++ b/lib/bcoin/address.js @@ -53,6 +53,12 @@ function Address(options) { */ Address.prototype.fromOptions = function fromOptions(options) { + if (typeof options === 'string') + return this.fromBase58(options); + + if (Buffer.isBuffer(options)) + return this.fromRaw(options); + return this.fromHash( options.hash, options.type, diff --git a/lib/bcoin/async.js b/lib/bcoin/async.js index 00ad2b13..37a67213 100644 --- a/lib/bcoin/async.js +++ b/lib/bcoin/async.js @@ -59,6 +59,8 @@ AsyncObject.prototype.open = function open(callback) { callback = utils.wrap(callback, unlock); } + this.emit('preopen'); + this.loading = true; this._open(function(err) { @@ -106,6 +108,8 @@ AsyncObject.prototype.close = function close(callback) { callback = utils.wrap(callback, unlock); } + this.emit('preclose'); + this.closing = true; this.loaded = false; diff --git a/lib/bcoin/bip151.js b/lib/bcoin/bip151.js index f25eb637..7555ab13 100644 --- a/lib/bcoin/bip151.js +++ b/lib/bcoin/bip151.js @@ -9,6 +9,7 @@ var EventEmitter = require('events').EventEmitter; var bcoin = require('./env'); var utils = require('./utils'); +var assert = utils.assert; var constants = bcoin.protocol.constants; var chachapoly = require('./chachapoly'); @@ -65,6 +66,7 @@ BIP151.prototype.init = function init(publicKey) { }; BIP151.prototype.rekey = function rekey() { + assert(this.mac, 'Cannot rekey before initialization.'); this.mac = utils.hash256(this.mac); this.k1 = this.mac.slice(0, 32); this.k2 = this.mac.slice(32, 64); @@ -166,12 +168,12 @@ BIP151.prototype.encack = function encack(data) { break; } - if (i === publicKey.length) + if (i === publicKey.length) { this.rekey(); - else - this.init(publicKey); + return; + } - return this; + this.init(publicKey); }; BIP151.prototype.feed = function feed(data) { diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 87698686..1a71a311 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -66,6 +66,8 @@ function Chain(options) { this.options = options; this.network = bcoin.network.get(options.network); + this.logger = options.logger || bcoin.defaultLogger; + this.profiler = options.profiler; this.db = new bcoin.chaindb(this, options); this.total = 0; this.currentBlock = null; @@ -101,7 +103,7 @@ Chain.prototype._init = function _init() { var self = this; this.locker.on('purge', function(total, size) { - bcoin.debug('Warning: %dmb of pending objects. Purging.', utils.mb(size)); + self.logger.warning('Warning: %dmb of pending objects. Purging.', utils.mb(size)); }); // Hook into events for debugging @@ -112,12 +114,12 @@ Chain.prototype._init = function _init() { if (self.options.spv) return; - bcoin.debug('Block %s (%d) added to chain.', + self.logger.info('Block %s (%d) added to chain.', utils.revHex(entry.hash), entry.height); }); this.on('competitor', function(block, entry) { - bcoin.debug('Heads up: Competing chain at height %d:' + self.logger.warning('Heads up: Competing chain at height %d:' + ' tip-height=%d competitor-height=%d' + ' tip-hash=%s competitor-hash=%s' + ' tip-chainwork=%s competitor-chainwork=%s' @@ -133,15 +135,17 @@ Chain.prototype._init = function _init() { }); this.on('resolved', function(block, entry) { - bcoin.debug('Orphan %s (%d) was resolved.', block.rhash, entry.height); + self.logger.debug('Orphan %s (%d) was resolved.', + block.rhash, entry.height); }); this.on('checkpoint', function(block, height) { - bcoin.debug('Hit checkpoint block %s (%d).', block.rhash, height); + self.logger.debug('Hit checkpoint block %s (%d).', + block.rhash, height); }); this.on('fork', function(block, height, expected) { - bcoin.debug( + self.logger.warning( 'Fork at height %d: expected=%s received=%s', height, utils.revHex(expected), @@ -150,19 +154,21 @@ Chain.prototype._init = function _init() { }); this.on('invalid', function(block, height) { - bcoin.debug('Invalid block at height %d: hash=%s', height, block.rhash); + self.logger.warning('Invalid block at height %d: hash=%s', + height, block.rhash); }); this.on('exists', function(block, height) { - bcoin.debug('Already have block %s (%d).', block.rhash, height); + self.logger.debug('Already have block %s (%d).', block.rhash, height); }); this.on('orphan', function(block, height) { - bcoin.debug('Handled orphan %s (%d).', block.rhash, height); + self.logger.debug('Handled orphan %s (%d).', block.rhash, height); }); this.on('purge', function(count, size) { - bcoin.debug('Warning: %d (%dmb) orphans cleared!', count, utils.mb(size)); + self.logger.debug('Warning: %d (%dmb) orphans cleared!', + count, utils.mb(size)); }); this.db.on('add entry', function(entry) { @@ -191,7 +197,7 @@ Chain.prototype._init = function _init() { Chain.prototype._open = function open(callback) { var self = this; - bcoin.debug('Chain is loading.'); + this.logger.info('Chain is loading.'); this.db.open(function(err) { if (err) @@ -215,19 +221,25 @@ Chain.prototype._open = function open(callback) { self.network.updateHeight(tip.height); } - bcoin.profiler.snapshot(); + if (self.profiler) + self.profiler.snapshot(); + + self.logger.memory(); self.getInitialState(function(err) { if (err) return callback(err); if (self.csvActive) - bcoin.debug('CSV is active.'); + self.logger.info('CSV is active.'); if (self.segwitActive) - bcoin.debug('Segwit is active.'); + self.logger.info('Segwit is active.'); - bcoin.profiler.snapshot(); + if (self.profiler) + self.profiler.snapshot(); + + self.logger.memory(); self.emit('tip', tip); @@ -290,7 +302,7 @@ Chain.prototype.preload = function preload(callback) { if (this.network.type !== 'main') return callback(); - bcoin.debug('Loading %s.', url); + this.logger.info('Loading %s.', url); function save(entry, header) { var unlock = locker.lock(save, [entry]); @@ -305,7 +317,7 @@ Chain.prototype.preload = function preload(callback) { } if ((++flushed % 50000) === 0) - utils.print('Flushed %d headers to DB.', flushed); + self.logger.info('Flushed %d headers to DB.', flushed); if (locker.jobs.length === 0 && ended) return callback(); @@ -330,7 +342,7 @@ Chain.prototype.preload = function preload(callback) { return callback(new Error('Bad response code: ' + res.statusCode)); } if (chainHeight > height - 30000) { - bcoin.debug('Preload height is %d. Skipping.', height); + self.logger.info('Preload height is %d. Skipping.', height); stream.destroy(); return callback(); } @@ -398,7 +410,7 @@ Chain.prototype.preload = function preload(callback) { save(entry, block); if ((height + 1) % 50000 === 0) - bcoin.debug('Received %d headers from electrum.org.', height + 1); + self.logger.info('Received %d headers.', height + 1); lastEntry = entry; height++; @@ -1213,7 +1225,7 @@ Chain.prototype.setBestChain = function setBestChain(entry, block, prev, callbac // A higher fork has arrived. // Time to reorganize the chain. - bcoin.debug('WARNING: Reorganizing chain.'); + self.logger.warning('WARNING: Reorganizing chain.'); return this.reorganize(entry, block, done); }; @@ -1459,7 +1471,7 @@ Chain.prototype.add = function add(block, callback, force) { try { block = block.toBlock(); } catch (e) { - bcoin.error(e); + self.logger.error(e); return done(new VerifyError(block, 'malformed', 'error parsing message', @@ -1533,7 +1545,9 @@ Chain.prototype.add = function add(block, callback, force) { // Take heap snapshot for debugging. if (self.total === 1 || self.total % 20 === 0) { utils.gc(); - bcoin.profiler.snapshot(); + if (self.profiler) + self.profiler.snapshot(); + self.logger.memory(); } utils.nextTick(function() { diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index eb123e59..0c365097 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -178,11 +178,10 @@ function ChainDB(chain, options) { this.options = options; this.chain = chain; + this.logger = chain.logger; this.network = this.chain.network; this.db = bcoin.ldb({ - network: this.network, - name: this.options.name || (this.options.spv ? 'spvchain' : 'chain'), location: this.options.location, db: this.options.db, compression: true, @@ -231,13 +230,13 @@ ChainDB.prototype._open = function open(callback) { var self = this; var genesis, block; - bcoin.debug('Starting chain load.'); + this.logger.info('Starting chain load.'); function done(err) { if (err) return callback(err); - bcoin.debug('Chain successfully loaded.'); + self.logger.info('Chain successfully loaded.'); self.db.checkVersion('V', 0, callback); } diff --git a/lib/bcoin/coin.js b/lib/bcoin/coin.js index d2d87149..2add595f 100644 --- a/lib/bcoin/coin.js +++ b/lib/bcoin/coin.js @@ -11,6 +11,7 @@ var bcoin = require('./env'); var utils = require('./utils'); var constants = bcoin.protocol.constants; var assert = utils.assert; +var Output = bcoin.output; /** * Represents an unspent output. @@ -44,7 +45,7 @@ function Coin(options) { this.fromOptions(options); } -utils.inherits(Coin, bcoin.output); +utils.inherits(Coin, Output); /** * Inject options into coin. diff --git a/lib/bcoin/ec.js b/lib/bcoin/ec.js index 9e4de545..66732cd0 100644 --- a/lib/bcoin/ec.js +++ b/lib/bcoin/ec.js @@ -240,6 +240,8 @@ ec.random = function random(size) { /** * Generate a random number within a range. + * Probably more cryptographically sound than + * `Math.random()`. * @param {Number} min - Inclusive. * @param {Number} max - Exclusive. * @returns {Number} diff --git a/lib/bcoin/env.js b/lib/bcoin/env.js index 814c78f0..1c2bb249 100644 --- a/lib/bcoin/env.js +++ b/lib/bcoin/env.js @@ -9,10 +9,6 @@ var utils = require('./utils'); var global = utils.global; -var fs; - -if (!utils.isBrowser) - fs = require('f' + 's'); /** * A BCoin "environment" which is used for @@ -32,7 +28,7 @@ if (!utils.isBrowser) * @param {String} [options.prefix=~/.bcoin] - Prefix for filesystem. * @param {String} [options.db=leveldb] - Database backend. * @param {Boolean} [options.debug=false] - Whether to display debug output. - * @param {String|Boolean} [options.debugFile=~/.bcoin/debug.log] - A file to + * @param {String|Boolean} [options.debugFile=~/.debug.log] - A file to * pipe debug output to. * @param {Boolean} [options.profile=false] - Enable profiler. * @param {Boolean} [options.useWorkers=false] - Enable workers. @@ -55,9 +51,10 @@ if (!utils.isBrowser) * @property {Object} ec - {@link module:ec}. * @property {Function} lru - {@link LRU} constructor. * @property {Function} bloom - {@link Bloom} constructor. - * @property {Function} bst - {@link BST} constructor. + * @property {Function} rbt - {@link RBT} constructor. * @property {Function} lowlevelup - See {@link LowlevelUp}. * @property {Function} uri - See {@link module:uri}. + * @property {Function} logger - {@link Logger} constructor. * * @property {Object} protocol * @property {Function} protocol.constants - See {@link module:constants}. @@ -115,19 +112,7 @@ if (!utils.isBrowser) * @property {Workers?} workerPool - Default global worker pool. */ -function Environment(options) { - if (!options) - options = {}; - - if (typeof options === 'string') - options = { network: options }; - - this.options = options; - - this._debug = null; - - this.isBrowser = utils.isBrowser; - +function Environment() { this.env = Environment; this.bn = require('bn.js'); this.utils = require('./utils'); @@ -140,14 +125,15 @@ function Environment(options) { this.rbt = require('./rbt'); this.lowlevelup = require('./lowlevelup'); this.uri = require('./uri'); + this.logger = require('./logger'); this.protocol = require('./protocol'); this.packets = this.protocol.packets; this.network = require('./network'); this.errors = require('./errors'); this.ldb = require('./ldb'); - this.profiler = require('./profiler'); this.timedata = require('./timedata'); + this.sigcache = require('./sigcache')(0); this.script = require('./script'); this.opcode = this.script.Opcode; this.stack = this.script.Stack; @@ -167,6 +153,7 @@ function Environment(options) { this.block = require('./block'); this.merkleblock = require('./merkleblock'); this.headers = require('./headers'); + this.fees = require('./fees'); this.node = require('./node'); this.spvnode = require('./spvnode'); this.fullnode = require('./fullnode'); @@ -188,28 +175,27 @@ function Environment(options) { this.miner = require('./miner'); this.minerblock = this.miner.MinerBlock; this.http = require('./http'); - - this.workers = null; - this.workerPool = null; - - this.prefix = null; - this.networkType = null; - this.db = null; - this.debugLogs = null; - this.debugFile = null; - this.profile = null; - this.useWorkers = null; - this.maxWorkers = null; - this.workerTimeout = null; - this.workerUri = null; - this.proxyServer = null; - this.logger = null; + this.workers = require('./workers'); this.time = new this.timedata(); + this.defaultLogger = new this.logger('none'); + this.useWorkers = false; + this.workerPool = new this.workers(); - this.set(options); + this.set({ + network: process.env.BCOIN_NETWORK || 'main', + useWorkers: +process.env.BCOIN_USE_WORKERS === 1, + maxWorkers: +process.env.BCOIN_MAX_WORKERS, + workerTimeout: +process.env.BCOIN_WORKER_TIMEOUT, + sigcacheSize: +process.env.BCOIN_SIGCACHE_SIZE + }); } +/** + * Set the default network. + * @param {String} options + */ + Environment.prototype.set = function set(options) { if (typeof options === 'string') options = { network: options }; @@ -217,209 +203,29 @@ Environment.prototype.set = function set(options) { if (!options) options = {}; - options = utils.merge({}, options); + if (options.network) + this.network.set(options.network); - options.network = options.network - || process.env.BCOIN_NETWORK - || 'main'; + if (typeof options.useWorkers === 'boolean') + this.useWorkers = options.useWorkers; - options.prefix = options.prefix - || process.env.BCOIN_PREFIX; + if (utils.isNumber(options.maxWorkers)) + this.workerPool.size = options.maxWorkers; - if (!options.prefix) - options.prefix = utils.HOME + '/.bcoin'; + if (utils.isNumber(options.workerTimeout)) + this.workerPool.timeout = options.workerTimeout; - if (!options.db) - options.db = process.env.BCOIN_DB; - - if (options.debug == null && process.env.BCOIN_DEBUG != null) - options.debug = +process.env.BCOIN_DEBUG === 1; - - if (options.debugFile == null && process.env.BCOIN_DEBUGFILE != null) { - if (process.env.BCOIN_DEBUGFILE === '0' - || process.env.BCOIN_DEBUGFILE === '1') { - options.debugFile = +process.env.BCOIN_DEBUGFILE !== 0; - } else { - options.debugFile = process.env.BCOIN_DEBUGFILE; - } - } - - if (options.profile == null && process.env.BCOIN_PROFILE != null) - options.profile = +process.env.BCOIN_PROFILE === 1; - - if (options.useWorkers == null && process.env.BCOIN_USE_WORKERS != null) - options.useWorkers = +process.env.BCOIN_USE_WORKERS === 1; - - if (options.maxWorkers == null && process.env.BCOIN_MAX_WORKERS != null) - options.maxWorkers = +process.env.BCOIN_MAX_WORKERS; - - if (options.workerTime == null && process.env.BCOIN_WORKER_TIMEOUT != null) - options.workerTimeout = +process.env.BCOIN_WORKER_TIMEOUT; - - if (options.debugFile && typeof options.debugFile !== 'string') { - options.debugFile = options.prefix; - if (options.network !== 'main') - options.debugFile += '/' + options.network; - options.debugFile += '/debug.log'; - } - - this.prefix = normalize(options.prefix); - this.networkType = options.network; - this.db = options.db; - this.debugLogs = !!options.debug; - this.debugFile = options.debugFile - ? normalize(options.debugFile) - : null; - this.profile = options.profile; - this.useWorkers = !!options.useWorkers; - this.maxWorkers = options.maxWorkers; - this.workerTimeout = options.workerTimeout; - this.workerUri = options.workerUri || '/bcoin-worker.js'; - this.proxyServer = options.proxyServer; - this.logger = options.logger; - - this.network.set(this.networkType); - - if (this.isBrowser && this.useWorkers) { + if (utils.isBrowser && this.useWorkers) { this.useWorkers = typeof global.Worker === 'function' || typeof global.postMessage === 'function'; } - if (this.useWorkers) { - this.workers = require('./workers'); - this.workerPool = new this.workers({ - size: this.maxWorkers, - timeout: this.workerTimeout, - network: this.network.get(this.network.primary) - }); - } + if (utils.isNumber(options.sigcacheSize)) + this.sigcache.size = options.sigcacheSize; return this; }; -/** - * Ensure a directory. - * @param {String} path - * @param {Boolean?} dirname - */ - -Environment.prototype.mkdir = function mkdir(path, dirname) { - if (this.isBrowser) - return; - - path = normalize(path, dirname); - - if (!mkdir.paths) - mkdir.paths = {}; - - if (mkdir.paths[path]) - return; - - mkdir.paths[path] = true; - - return mkdirp(path); -}; - -/** - * Output a debug message. - * @param {Object|String} obj - * @param {...String} args - * @example - * bcoin.debug('foo: %d', 10); - */ - -Environment.prototype.debug = function debug() { - var args = new Array(arguments.length); - var i, msg; - - for (i = 0; i < args.length; i++) - args[i] = arguments[i]; - - if (this.logger) { - if (this.debugLogs) - this.logger.debug(args); - return; - } - - if (this.isBrowser) { - if (this.debugLogs) { - msg = typeof args[0] !== 'object' - ? utils.format(args, false) - : args[0]; - console.log(msg); - } - return; - } - - if (this.debugLogs) { - msg = utils.format(args, true); - process.stderr.write(msg + '\n'); - } - - if (this.debugFile) { - msg = utils.format(args, false); - this.write(msg); - } -}; - -/** - * Output an error. - * @param {Error} err - */ - -Environment.prototype.error = function error(err) { - var msg; - - if (!err) - return; - - if (typeof err === 'string') - err = new Error(err); - - if (this.logger) { - if (this.debugLogs) - this.logger.error(err); - return; - } - - if (this.isBrowser) { - if (this.debugLogs) - console.error(err); - return; - } - - if (this.debugLogs) { - msg = (err.message + '').replace(/^ *Error: */, ''); - - if (process.stdout && process.stdout.isTTY) - msg = '\x1b[1;31m[Error]\x1b[m ' + msg; - else - msg = '[Error] ' + msg; - - process.stderr.write(msg + '\n'); - } - - if (this.debugFile) - this.write(err.stack + ''); -}; - -/** - * Write a message to the debug log. - * @param {String} msg - */ - -Environment.prototype.write = function write(msg) { - if (this.isBrowser) - return; - - if (!this._debug) { - this.mkdir(this.debugFile, true); - this._debug = fs.createWriteStream(this.debugFile, { flags: 'a' }); - } - - this._debug.write(process.pid + ' (' + utils.date() + '): ' + msg + '\n'); -}; - /** * Get the adjusted time. * @returns {Number} Adjusted time. @@ -429,74 +235,12 @@ Environment.prototype.now = function now() { return this.time.now(); }; -/** - * Normalize a path. - * @param {String} path - * @param {Boolean?} dirname - */ - -function normalize(path, dirname) { - var parts; - - path = path.replace(/\\/g, '/'); - path = path.replace(/\/+$/, ''); - parts = path.split(/\/+/); - - if (dirname) - parts.pop(); - - return parts.join('/'); -} - -/** - * Create a full directory structure. - * @param {String} path - */ - -function mkdirp(path) { - var i, parts, stat; - - if (!fs) - return; - - path = path.replace(/\\/g, '/'); - path = path.replace(/\/+$/, ''); - parts = path.split(/\/+/); - path = ''; - - if (process.platform === 'win32') { - if (parts[0].indexOf(':') !== -1) - path = parts.shift() + '/'; - } - - if (parts[0].length === 0) { - parts.shift(); - path = '/'; - } - - for (i = 0; i < parts.length; i++) { - path += parts[i]; - - try { - stat = fs.statSync(path); - if (!stat.isDirectory()) - throw new Error('Could not create directory.'); - } catch (e) { - if (e.code === 'ENOENT') - fs.mkdirSync(path, 488 /* 0750 */); - else - throw e; - } - - path += '/'; - } -} - /* * Expose by converting `exports` to an * Environment. */ -utils.merge(exports, Environment.prototype); +exports.set = Environment.prototype.set; +exports.now = Environment.prototype.now; Environment.call(exports); diff --git a/lib/bcoin/fees.js b/lib/bcoin/fees.js index 13cc30cc..aabc3018 100644 --- a/lib/bcoin/fees.js +++ b/lib/bcoin/fees.js @@ -48,12 +48,13 @@ var FREE_THRESHOLD = constants.tx.FREE_THRESHOLD; * @param {String} type */ -function ConfirmStats(buckets, maxConfirms, decay, type) { +function ConfirmStats(buckets, maxConfirms, decay, type, logger) { var i; if (!(this instanceof ConfirmStats)) - return new ConfirmStats(buckets, maxConfirms, decay, type); + return new ConfirmStats(buckets, maxConfirms, decay, type, logger); + this.logger = logger || bcoin.defaultLogger; this.maxConfirms = maxConfirms; this.decay = decay; this.type = type; @@ -214,7 +215,7 @@ ConfirmStats.prototype.estimateMedian = function estimateMedian(target, needed, } } - // bcoin.debug('estimatefee: ' + // this.logger.debug('estimatefee: ' // + ' For confirmation success in %d blocks' // + ' %s %d need %s %s: %d from buckets %d - %d.' // + ' Current bucket stats %d% %d/%d (%d mempool).', @@ -245,7 +246,7 @@ ConfirmStats.prototype.addTX = function addTX(height, val) { var bucketIndex = this.bucketMap.search(val); var blockIndex = height % this.unconfTX.length; this.unconfTX[blockIndex][bucketIndex]++; - bcoin.debug('estimatefee: Adding tx to %s.', this.type); + this.logger.debug('estimatefee: Adding tx to %s.', this.type); return bucketIndex; }; @@ -264,7 +265,7 @@ ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, buc blocksAgo = 0; if (blocksAgo < 0) { - bcoin.debug('estimatefee: Blocks ago is negative for mempool tx.'); + this.logger.debug('estimatefee: Blocks ago is negative for mempool tx.'); return; } @@ -272,7 +273,7 @@ ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, buc if (this.oldUnconfTX[bucketIndex] > 0) { this.oldUnconfTX[bucketIndex]--; } else { - bcoin.debug('estimatefee:' + this.logger.debug('estimatefee:' + ' Mempool tx removed >25 blocks (bucket=%d).', bucketIndex); } @@ -281,7 +282,7 @@ ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, buc if (this.unconfTX[blockIndex][bucketIndex] > 0) { this.unconfTX[blockIndex][bucketIndex]--; } else { - bcoin.debug('estimatefee:' + this.logger.debug('estimatefee:' + ' Mempool tx removed (block=%d, bucket=%d).', blockIndex, bucketIndex); } @@ -293,8 +294,8 @@ ConfirmStats.prototype.removeTX = function removeTX(entryHeight, bestHeight, buc * @returns {Buffer} */ -ConfirmStats.prototype.toRaw = function toRaw() { - var p = new BufferWriter(); +ConfirmStats.prototype.toRaw = function toRaw(writer) { + var p = new BufferWriter(writer); var i; function writeArray(buckets) { @@ -315,7 +316,10 @@ ConfirmStats.prototype.toRaw = function toRaw() { for (i = 0; i < this.maxConfirms; i++) writeArray(this.confAvg[i]); - return p.render(); + if (!writer) + p = p.render(); + + return p; }; /** @@ -325,7 +329,7 @@ ConfirmStats.prototype.toRaw = function toRaw() { * @returns {ConfirmStats} */ -ConfirmStats.fromRaw = function fromRaw(data, type) { +ConfirmStats.fromRaw = function fromRaw(data, type, logger) { var p = new BufferReader(data); var i, decay, buckets, avg, txAvg, maxConfirms, confAvg, stats; @@ -369,7 +373,7 @@ ConfirmStats.fromRaw = function fromRaw(data, type) { throw new Error('Mismatch in fee/pri conf average bucket count.'); } - stats = new ConfirmStats(buckets, maxConfirms, decay, type); + stats = new ConfirmStats(buckets, maxConfirms, decay, type, logger); stats.avg = avg; stats.txAvg = txAvg; @@ -386,19 +390,25 @@ ConfirmStats.fromRaw = function fromRaw(data, type) { * @param {Network|NetworkType} network */ -function PolicyEstimator(minRelay, network) { +function PolicyEstimator(minRelay, network, logger) { var fee, priority, boundary; if (!(this instanceof PolicyEstimator)) - return new PolicyEstimator(minRelay, network); + return new PolicyEstimator(minRelay, network, logger); + + fee = []; + priority = []; this.network = bcoin.network.get(network); + this.logger = logger || bcoin.defaultLogger; this.minTrackedFee = minRelay < MIN_FEERATE ? MIN_FEERATE : minRelay; - fee = []; + this.minTrackedPri = FREE_THRESHOLD < MIN_PRIORITY + ? MIN_PRIORITY + : FREE_THRESHOLD; for (boundary = this.minTrackedFee; boundary <= MAX_FEERATE; @@ -408,16 +418,6 @@ function PolicyEstimator(minRelay, network) { fee.push(INF_FEERATE); - this.feeStats = new ConfirmStats( - fee, MAX_BLOCK_CONFIRMS, - DEFAULT_DECAY, 'FeeRate'); - - this.minTrackedPri = FREE_THRESHOLD < MIN_PRIORITY - ? MIN_PRIORITY - : FREE_THRESHOLD; - - priority = []; - for (boundary = this.minTrackedPri; boundary <= MAX_PRIORITY; boundary *= PRI_SPACING) { @@ -426,15 +426,22 @@ function PolicyEstimator(minRelay, network) { priority.push(INF_PRIORITY); + this.feeStats = new ConfirmStats( + fee, MAX_BLOCK_CONFIRMS, + DEFAULT_DECAY, 'FeeRate', + this.logger); + this.priStats = new ConfirmStats( priority, MAX_BLOCK_CONFIRMS, - DEFAULT_DECAY, 'Priority'); + DEFAULT_DECAY, 'Priority', + this.logger); this.feeUnlikely = 0; this.feeLikely = INF_FEERATE; this.priUnlikely = 0; this.priLikely = INF_PRIORITY; this.map = {}; + this.mapSize = 0; this.bestHeight = 0; } @@ -447,7 +454,7 @@ PolicyEstimator.prototype.removeTX = function removeTX(hash) { var item = this.map[hash]; if (!item) { - bcoin.debug( + this.logger.debug( 'estimatefee: Mempool tx %s not found.', utils.revHex(hash)); return; @@ -456,6 +463,7 @@ PolicyEstimator.prototype.removeTX = function removeTX(hash) { this.feeStats.removeTX(item.blockHeight, this.bestHeight, item.bucketIndex); delete this.map[hash]; + this.mapSize--; }; /** @@ -500,7 +508,7 @@ PolicyEstimator.prototype.processTX = function processTX(entry, current) { var fee, rate, priority; if (this.map[hash]) { - bcoin.debug('estimatefee: Mempool tx %s already tracked.', entry.tx.rhash); + this.logger.debug('estimatefee: Mempool tx %s already tracked.', entry.tx.rhash); return; } @@ -520,23 +528,24 @@ PolicyEstimator.prototype.processTX = function processTX(entry, current) { rate = entry.getRate(); priority = entry.getPriority(height); - bcoin.debug('estimatefee: Processing mempool tx %s.', entry.tx.rhash); + this.logger.debug('estimatefee: Processing mempool tx %s.', entry.tx.rhash); if (fee === 0 || this.isPriPoint(rate, priority)) { this.map[hash] = { blockHeight: height, bucketIndex: this.priStats.addTX(height, priority) }; + this.mapSize++; } else if (this.isFeePoint(rate, priority)) { this.map[hash] = { blockHeight: height, bucketIndex: this.feeStats.addTX(height, rate) }; - bcoin.debug('estimatefee: Rate: %d.', this.estimateFee()); + this.mapSize++; + this.logger.debug('estimatefee: Rate: %d.', this.estimateFee()); } else { - bcoin.debug('estimatefee: Not adding tx %s.', entry.tx.rhash); + this.logger.debug('estimatefee: Not adding tx %s.', entry.tx.rhash); } - }; /** @@ -554,7 +563,7 @@ PolicyEstimator.prototype.processBlockTX = function processBlockTX(height, entry blocks = height - entry.height; if (blocks <= 0) { - bcoin.debug( + this.logger.debug( 'estimatefee: Block tx %s had negative blocks to confirm (%d, %d).', entry.tx.rhash, height, @@ -595,7 +604,7 @@ PolicyEstimator.prototype.processBlock = function processBlock(height, entries, if (!current) return; - bcoin.debug('estimatefee: Recalculating dynamic cutoffs.'); + this.logger.debug('estimatefee: Recalculating dynamic cutoffs.'); this.feeLikely = this.feeStats.estimateMedian( 2, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, @@ -634,9 +643,9 @@ PolicyEstimator.prototype.processBlock = function processBlock(height, entries, this.feeStats.updateAverages(); this.priStats.updateAverages(); - bcoin.debug('estimatefee: Done updating estimates' + this.logger.debug('estimatefee: Done updating estimates' + ' for %d confirmed entries. New mempool map size %d.', - entries.length, Object.keys(this.map).length); + entries.length, this.mapSize); }; /** @@ -738,12 +747,18 @@ PolicyEstimator.prototype.estimatePriority = function estimatePriority(target, s * @returns {Buffer} */ -PolicyEstimator.prototype.toRaw = function toRaw() { - var p = new BufferWriter(); +PolicyEstimator.prototype.toRaw = function toRaw(writer) { + var p = new BufferWriter(writer); + + p.writeU32(this.network.magic); p.writeU32(this.bestHeight); p.writeVarBytes(this.feeStats.toRaw()); p.writeVarBytes(this.priStats.toRaw()); - return p.render(); + + if (!writer) + p = p.render(); + + return p; }; /** @@ -754,12 +769,13 @@ PolicyEstimator.prototype.toRaw = function toRaw() { * @returns {PolicyEstimator} */ -PolicyEstimator.fromRaw = function fromRaw(data, minRelay, network) { +PolicyEstimator.fromRaw = function fromRaw(minRelay, data, logger) { var p = new BufferReader(data); + var network = bcoin.network.fromMagic(p.readU32()); var bestHeight = p.readU32(); - var feeStats = ConfirmStats.fromRaw(p.readVarBytes(), 'FeeRate'); - var priStats = ConfirmStats.fromRaw(p.readVarBytes(), 'Priority'); - var estimator = new PolicyEstimator(minRelay, network); + var estimator = new PolicyEstimator(minRelay, network, logger); + var feeStats = ConfirmStats.fromRaw(p.readVarBytes(), 'FeeRate', logger); + var priStats = ConfirmStats.fromRaw(p.readVarBytes(), 'Priority', logger); estimator.bestHeight = bestHeight; estimator.feeStats = feeStats; diff --git a/lib/bcoin/fullnode.js b/lib/bcoin/fullnode.js index fae46f32..e9d6fa23 100644 --- a/lib/bcoin/fullnode.js +++ b/lib/bcoin/fullnode.js @@ -8,6 +8,7 @@ 'use strict'; var bcoin = require('./env'); +var constants = bcoin.protocol.constants; var utils = require('./utils'); var assert = utils.assert; var Node = bcoin.node; @@ -34,6 +35,7 @@ var Node = bcoin.node; * @param {Object?} options.wallet - Primary {@link Wallet} options. * @property {Boolean} loaded * @property {Chain} chain + * @property {PolicyEstimator} fees * @property {Mempool} mempool * @property {Pool} pool * @property {Miner} miner @@ -50,18 +52,33 @@ function Fullnode(options) { Node.call(this, options); + // Instantiate blockchain. this.chain = new bcoin.chain({ network: this.network, + logger: this.logger, + profiler: this.profiler, + db: this.db, + location: this.location('chain'), preload: false, spv: false, prune: this.options.prune, useCheckpoints: this.options.useCheckpoints }); + // Fee estimation. + this.fees = new bcoin.fees( + constants.tx.MIN_RELAY, + this.network, + this.logger); + // Mempool needs access to the chain. this.mempool = new bcoin.mempool({ network: this.network, + logger: this.logger, chain: this.chain, + fees: this.fees, + db: 'memory', + location: this.location('mempool'), limitFree: this.options.limitFree, limitFreeRelay: this.options.limitFreeRelay, requireStandard: this.options.requireStandard, @@ -72,26 +89,36 @@ function Fullnode(options) { // Pool needs access to the chain and mempool. this.pool = new bcoin.pool({ network: this.network, + logger: this.logger, chain: this.chain, mempool: this.mempool, witness: this.network.witness, selfish: this.options.selfish, headers: this.options.headers, + proxyServer: this.options.proxyServer, + preferredSeed: this.options.preferredSeed, spv: false }); // Miner needs access to the chain and mempool. this.miner = new bcoin.miner({ network: this.network, + logger: this.logger, chain: this.chain, mempool: this.mempool, + fees: this.fees, address: this.options.payoutAddress, coinbaseFlags: this.options.coinbaseFlags, parallel: this.options.parallel }); + // Wallet database needs access to fees. this.walletdb = new bcoin.walletdb({ network: this.network, + logger: this.logger, + fees: this.fees, + db: this.db, + location: this.location('walletdb'), verify: false }); @@ -99,6 +126,7 @@ function Fullnode(options) { if (!utils.isBrowser) { this.http = new bcoin.http.server({ network: this.network, + logger: this.logger, node: this, key: this.options.sslKey, cert: this.options.sslCert, @@ -122,28 +150,28 @@ Fullnode.prototype._init = function _init() { // Bind to errors this.mempool.on('error', function(err) { - self.emit('error', err); + self._error(err); }); this.miner.on('error', function(err) { - self.emit('error', err); + self._error(err); }); this.pool.on('error', function(err) { - self.emit('error', err); + self._error(err); }); this.chain.on('error', function(err) { - self.emit('error', err); + self._error(err); }); this.walletdb.on('error', function(err) { - self.emit('error', err); + self._error(err); }); if (this.http) { this.http.on('error', function(err) { - self.emit('error', err); + self._error(err); }); } @@ -154,7 +182,7 @@ Fullnode.prototype._init = function _init() { this.on('tx', function(tx) { self.walletdb.addTX(tx, function(err) { if (err) - self.emit('error', err); + self._error(err); }); }); @@ -175,14 +203,14 @@ Fullnode.prototype._init = function _init() { return; self.mempool.addBlock(block, function(err) { if (err) - self.emit('error', err); + self._error(err); }); }); this.chain.on('remove block', function(block) { self.walletdb.removeBlock(block, function(err) { if (err) - self.emit('error', err); + self._error(err); }); if (!self.chain.isFull()) @@ -190,7 +218,7 @@ Fullnode.prototype._init = function _init() { self.mempool.removeBlock(block, function(err) { if (err) - self.emit('error', err); + self._error(err); }); }); @@ -214,7 +242,7 @@ Fullnode.prototype._open = function open(callback) { if (err) return callback(err); - bcoin.debug('Node is loaded.'); + self.logger.info('Node is loaded.'); callback(); } @@ -256,7 +284,7 @@ Fullnode.prototype._open = function open(callback) { return next(err); if (txs.length > 0) - bcoin.debug('Rebroadcasting %d transactions.', txs.length); + self.logger.info('Rebroadcasting %d transactions.', txs.length); for (i = 0; i < txs.length; i++) self.pool.broadcast(txs[i]); @@ -385,13 +413,14 @@ Fullnode.prototype.stopSync = function stopSync() { */ Fullnode.prototype.createWallet = function createWallet(options, callback) { + var self = this; this.walletdb.ensure(options, function(err, wallet) { if (err) return callback(err); assert(wallet); - bcoin.debug('Loaded wallet with id=%s address=%s', + self.logger.info('Loaded wallet with id=%s address=%s', wallet.id, wallet.getAddress()); return callback(null, wallet); diff --git a/lib/bcoin/http/client.js b/lib/bcoin/http/client.js index 5186d5ae..dac2eeab 100644 --- a/lib/bcoin/http/client.js +++ b/lib/bcoin/http/client.js @@ -73,8 +73,6 @@ HTTPClient.prototype._open = function _open(callback) { this.socket.on('connect', function() { self.socket.on('version', function(info) { - bcoin.debug('Connected to bcoin server: %s (%s)', - info.version, info.network); assert(info.network === self.network.type, 'Wrong network.'); }); diff --git a/lib/bcoin/http/server.js b/lib/bcoin/http/server.js index 159bf4c6..d133478c 100644 --- a/lib/bcoin/http/server.js +++ b/lib/bcoin/http/server.js @@ -41,6 +41,7 @@ function HTTPServer(options) { this.walletdb = this.node.walletdb; this.mempool = this.node.mempool; this.pool = this.node.pool; + this.logger = options.logger || this.node.logger; this.loaded = false; options.sockets = true; @@ -61,7 +62,7 @@ HTTPServer.prototype._init = function _init() { var self = this; this.server.on('request', function(req, res) { - bcoin.debug('Request from %s path=%s', + self.logger.debug('Request from %s path=%s', req.socket.remoteAddress, req.pathname); }); @@ -90,8 +91,8 @@ HTTPServer.prototype._init = function _init() { var params = utils.merge({}, req.params, req.query, req.body); var options = {}; - bcoin.debug('Params:'); - bcoin.debug(params); + self.logger.debug('Params:'); + self.logger.debug(params); if (params.id) { assert(params.id !== '!all'); @@ -884,7 +885,7 @@ HTTPServer.prototype.listen = function listen(port, host, callback) { return self.emit('error', err); } - bcoin.debug('HTTP server listening on %s (port=%d).', + self.logger.info('HTTP server listening on %s (port=%d).', address.address, address.port); self.loaded = true; diff --git a/lib/bcoin/ldb.js b/lib/bcoin/ldb.js index 936a3a60..a4cf0705 100644 --- a/lib/bcoin/ldb.js +++ b/lib/bcoin/ldb.js @@ -9,11 +9,9 @@ 'use strict'; -var bcoin = require('./env'); var LowlevelUp = require('./lowlevelup'); -var utils = bcoin.utils; +var utils = require('./utils'); var assert = utils.assert; -var db = {}; /** * @param {Object} options @@ -33,32 +31,28 @@ var db = {}; function ldb(options) { options = ldb.parseOptions(options); - if (!db[options.location]) { - if (options.backend !== 'rbt' && !bcoin.isBrowser) - bcoin.mkdir(options.location, true); + if (options.backend !== 'rbt') + utils.mkdir(options.location, true); - db[options.location] = new LowlevelUp(options.location, { - // LevelDB and others - createIfMissing: true, - errorIfExists: false, - compression: options.compression !== false, - cacheSize: options.cacheSize || (8 << 20), - writeBufferSize: options.writeBufferSize || (4 << 20), - maxOpenFiles: options.maxOpenFiles || 8192, - filterBits: 0, - paranoidChecks: false, - memory: false, + return new LowlevelUp(options.location, { + // LevelDB and others + createIfMissing: true, + errorIfExists: false, + compression: options.compression !== false, + cacheSize: options.cacheSize || (8 << 20), + writeBufferSize: options.writeBufferSize || (4 << 20), + maxOpenFiles: options.maxOpenFiles || 8192, + filterBits: 0, + paranoidChecks: false, + memory: false, - // For LMDB if we decide to use it: - sync: options.sync || false, - mapSize: options.mapSize || 300 * (1024 << 20), - writeMap: options.writeMap || false, + // For LMDB if we decide to use it: + sync: options.sync || false, + mapSize: options.mapSize || 300 * (1024 << 20), + writeMap: options.writeMap || false, - db: options.db - }); - } - - return db[options.location]; + db: options.db + }); } /** @@ -71,7 +65,7 @@ ldb.getBackend = function getBackend(db) { var name, ext; if (!db) - db = bcoin.db || 'leveldb'; + db = 'memory'; if (db === 'leveldb') name = 'leveldown'; @@ -112,30 +106,26 @@ ldb.getBackend = function getBackend(db) { */ ldb.parseOptions = function parseOptions(options) { - var network = bcoin.network.get(options.network); var backend = ldb.getBackend(options.db); var location = options.location; var db; - if (!location) { - assert(typeof options.name === 'string', 'Name or location required.'); - location = bcoin.prefix; - if (network.type !== 'main') - location += '/' + network.type; - location += '/' + options.name + '.' + backend.ext; - } - if (backend.name === 'rbt') db = require('./rbt'); - else if (bcoin.isBrowser) + else if (utils.isBrowser) db = require('level-js'); else db = require(backend.name); + if (typeof location !== 'string') { + assert(backend.name === 'rbt', 'Location required.'); + location = 'rbt'; + } + return utils.merge({}, options, { backend: backend.name, ext: backend.ext, - location: location, + location: location + '.' + backend.ext, db: db }); }; diff --git a/lib/bcoin/logger.js b/lib/bcoin/logger.js new file mode 100644 index 00000000..6873ac58 --- /dev/null +++ b/lib/bcoin/logger.js @@ -0,0 +1,311 @@ +/*! + * logger.js - basic logger for bcoin + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var utils = require('./utils'); +var assert = require('assert'); +var fs; + +if (!utils.isBrowser) + fs = require('f' + 's'); + +/** + * Basic stdout and file logger. + * @exports Logger + * @constructor + * @param {(String|Object)?} options/level + * @param {String?} options.level + * @param {Boolean} [options.colors=true] + */ + +function Logger(options) { + if (!(this instanceof Logger)) + return new Logger(options); + + if (!options) + options = {}; + + if (typeof options === 'string') + options = { level: options }; + + this.level = Logger.levels.warning; + this.colors = options.colors !== false; + this.file = options.file; + this.stream = null; + this.closed = false; + + if (!process.stdout || !process.stdout.isTTY) + this.colors = false; + + if (options.level != null) + this.setLevel(options.level); +} + +/** + * Available log levels. + * @enum {Number} + */ + +Logger.levels = { + none: 0, + error: 1, + warning: 2, + info: 3, + debug: 4 +}; + +/** + * Default CSI colors. + * @enum {String} + */ + +Logger.colors = { + error: '1;31', + warning: '1;33', + info: '94', + debug: '90' +}; + +/** + * Open the logger. + */ + +Logger.prototype.open = function open() { + this.closed = false; +}; + +/** + * Destroy the write stream. + */ + +Logger.prototype.close = function close() { + if (!this.stream) + return; + + try { + this.stream.destroy(); + } catch (e) { + ; + } + + this.closed = true; + this.stream = null; +}; + +/** + * Set or reset the log level. + * @param {String} level + */ + +Logger.prototype.setLevel = function setLevel(level) { + level = Logger.levels[level]; + assert(level != null, 'Invalid log level.'); + this.level = level; +}; + +/** + * Output a log to the `error` log level. + * @param {String|Object|Error} err + * @param {...Object} args + */ + +Logger.prototype.error = function error(err) { + var i, args; + + if (this.level < Logger.levels.error) + return; + + if (err instanceof Error) + return this._error(err); + + args = new Array(arguments.length); + + for (i = 0; i < args.length; i++) + args[i] = arguments[i]; + + this.log('error', args); +}; + +/** + * Output a log to the `warning` log level. + * @param {String|Object} obj + * @param {...Object} args + */ + +Logger.prototype.warning = function warning() { + var i, args; + + if (this.level < Logger.levels.warning) + return; + + args = new Array(arguments.length); + + for (i = 0; i < args.length; i++) + args[i] = arguments[i]; + + this.log('warning', args); +}; + +/** + * Output a log to the `info` log level. + * @param {String|Object} obj + * @param {...Object} args + */ + +Logger.prototype.info = function info() { + var i, args; + + if (this.level < Logger.levels.info) + return; + + args = new Array(arguments.length); + + for (i = 0; i < args.length; i++) + args[i] = arguments[i]; + + this.log('info', args); +}; + +/** + * Output a log to the `debug` log level. + * @param {String|Object} obj + * @param {...Object} args + */ + +Logger.prototype.debug = function debug() { + var i, args; + + if (this.level < Logger.levels.debug) + return; + + args = new Array(arguments.length); + + for (i = 0; i < args.length; i++) + args[i] = arguments[i]; + + this.log('debug', args); +}; + +/** + * Output a log to the desired log level. + * Note that this bypasses the level check. + * @param {String} level + * @param {Object[]} args + */ + +Logger.prototype.log = function log(level, args) { + var prefix, color, msg; + + if (this.closed) + return; + + assert(Logger.levels[level] != null, 'Invalid log level.'); + + prefix = '[' + level + '] '; + + if (utils.isBrowser) { + msg = typeof args[0] !== 'object' + ? utils.format(args, false) + : args[0]; + + msg = prefix + msg; + + if (level === 'error') + console.error(msg); + else + console.log(msg); + + return; + } + + if (this.colors) { + color = Logger.colors[level]; + prefix = '\x1b[' + color + 'm' + prefix + '\x1b[m'; + } + + msg = prefix + utils.format(args, this.colors); + + if (level === 'error') + process.stderr.write(msg + '\n'); + else + process.stdout.write(msg + '\n'); + + if (this.file) { + if (this.colors) + msg = prefix + utils.format(args, false); + this.write(msg); + } +}; + +/** + * Write a string to the output stream (usually a file). + * @param {String} msg + */ + +Logger.prototype.write = function write(msg) { + if (!fs) + return; + + if (this.closed) + return; + + if (!this.stream) { + utils.mkdir(this.file, true); + this.stream = fs.createWriteStream(this.file, { flags: 'a' }); + this.stream.on('error', function() {}); + } + + this.stream.write(process.pid + ' (' + utils.date() + '): ' + msg + '\n'); +}; + +/** + * Helper to parse an error into a nicer + * format. Call's `log` internally. + * @private + * @param {Error} err + */ + +Logger.prototype._error = function error(err) { + var msg; + + if (utils.isBrowser) { + console.error(err); + return; + } + + msg = (err.message + '').replace(/^ *Error: */, ''); + + this.log('error', [msg]); + + if (this.file) + this.write(err.stack + ''); +}; + +/** + * Log the current memory usage. + */ + +Logger.prototype.memory = function memory() { + var mem; + + if (!process.memoryUsage) + return; + + mem = process.memoryUsage(); + + this.debug('Memory: rss=%dmb, js-heap=%d/%dmb native-heap=%dmb', + utils.mb(mem.rss), + utils.mb(mem.heapUsed), + utils.mb(mem.heapTotal), + utils.mb(mem.rss - mem.heapTotal)); +}; + +/* + * Expose + */ + +module.exports = Logger; diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 90582f9c..982f435b 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -70,17 +70,17 @@ function Mempool(options) { this.options = options; this.chain = options.chain; + this.fees = options.fees; assert(this.chain, 'Mempool requires a blockchain.'); this.network = this.chain.network; + this.logger = options.logger || this.chain.logger; this.loaded = false; this.locker = new bcoin.locker(this, this.addTX); this.db = bcoin.ldb({ - network: this.network, - name: this.options.name || 'mempool', location: this.options.location, db: this.options.db || 'memory' }); @@ -243,7 +243,8 @@ Mempool.prototype.addBlock = function addBlock(block, callback, force) { self.blockSinceBump = true; self.lastFeeUpdate = utils.now(); - self.network.fees.processBlock(block.height, entries, self.chain.isFull()); + if (self.fees) + self.fees.processBlock(block.height, entries, self.chain.isFull()); return callback(); }); @@ -373,7 +374,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() { hash = orphans[i]; orphans.splice(i, 1); - bcoin.debug('Removing orphan %s from mempool.', utils.revHex(hash)); + this.logger.debug('Removing orphan %s from mempool.', utils.revHex(hash)); this.removeOrphan(hash); } @@ -830,9 +831,10 @@ Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) { self.emit('tx', entry.tx); self.emit('add tx', entry.tx); - self.network.fees.processTX(entry, self.chain.isFull()); + if (self.fees) + self.fees.processTX(entry, self.chain.isFull()); - bcoin.debug('Added tx %s to the mempool.', entry.tx.rhash); + self.logger.debug('Added tx %s to the mempool.', entry.tx.rhash); resolved = self.resolveOrphans(entry.tx); @@ -841,7 +843,7 @@ Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) { self.verify(entry, function(err) { if (err) { if (err.type === 'VerifyError') { - bcoin.debug('Could not resolve orphan %s: %s.', + self.logger.debug('Could not resolve orphan %s: %s.', tx.rhash, err.message); self.emit('bad orphan', tx, entry); @@ -855,7 +857,7 @@ Mempool.prototype.addUnchecked = function addUnchecked(entry, callback, force) { self.emit('error', err); return next(); } - bcoin.debug('Resolved orphan %s in mempool.', entry.tx.rhash); + self.logger.debug('Resolved orphan %s in mempool.', entry.tx.rhash); next(); }, true); }); @@ -897,7 +899,8 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit, callb self.size -= self.memUsage(entry.tx); self.total--; - self.network.fees.removeTX(hash); + if (self.fees) + self.fees.removeTX(hash); if (limit) { rate = bcoin.tx.getRate(entry.sizes, entry.fees); @@ -1144,7 +1147,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx) { var i, hash, input, prev; if (tx.getSize() > 5000) { - bcoin.debug('Ignoring large orphan: %s', tx.rhash); + this.logger.debug('Ignoring large orphan: %s', tx.rhash); this.emit('bad orphan', tx); return; } @@ -1171,7 +1174,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx) { this.orphans[hash] = tx.toExtended(true); this.totalOrphans++; - bcoin.debug('Added orphan %s to mempool.', tx.rhash); + this.logger.debug('Added orphan %s to mempool.', tx.rhash); this.emit('add orphan', tx); @@ -1242,7 +1245,7 @@ Mempool.prototype.getOrphan = function getOrphan(hash) { orphan = bcoin.tx.fromExtended(orphan, true); } catch (e) { delete this.orphans[hash]; - bcoin.debug('%s %s', + this.logger.warning('%s %s', 'Warning: possible memory corruption.', 'Orphan failed deserialization.'); return; diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index e408faf1..105f8de8 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -40,12 +40,14 @@ function Miner(options) { options = {}; this.options = options; - this.address = this.options.address; + this.address = bcoin.address(this.options.address); this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin'; this.pool = options.pool; this.chain = options.chain; + this.logger = options.logger || this.chain.logger; this.mempool = options.mempool; + this.fees = this.mempool ? this.mempool.fees : options.fees; assert(this.chain, 'Miner requires a blockchain.'); @@ -57,7 +59,6 @@ function Miner(options) { if (bcoin.useWorkers) { this.workerPool = new bcoin.workers({ - network: this.network, size: this.options.parallel ? 2 : 1, timeout: -1 }); @@ -103,12 +104,12 @@ Miner.prototype._init = function _init() { this.on('block', function(block) { // Emit the block hex as a failsafe (in case we can't send it) - bcoin.debug('Found block: %d (%s).', block.height, block.rhash); - bcoin.debug('Raw: %s', block.toRaw().toString('hex')); + self.logger.info('Found block: %d (%s).', block.height, block.rhash); + self.logger.debug('Raw: %s', block.toRaw().toString('hex')); }); this.on('status', function(stat) { - bcoin.debug( + self.logger.info( 'Miner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s', stat.hashrate / 1000 | 0, stat.hashes, @@ -177,7 +178,7 @@ Miner.prototype.start = function start(version) { self.chain.add(block, function(err) { if (err) { if (err.type === 'VerifyError') - bcoin.debug('%s could not be added to chain.', block.rhash); + self.logger.warning('%s could not be added to chain.', block.rhash); self.emit('error', err); return self.start(); } @@ -700,10 +701,11 @@ MinerBlock.prototype.toRaw = function toRaw(writer) { var p = new BufferWriter(writer); var i; + p.writeU32(this.network.magic); p.writeBytes(this.tip.toRaw()); p.writeU32(this.block.version); p.writeU32(this.block.bits); - p.writeVarString(this.address, 'ascii'); + p.writeVarBytes(this.address.toRaw()); p.writeVarString(this.coinbaseFlags, 'utf8'); p.writeU8(this.witness ? 1 : 0); p.writeVarint(this.block.txs.length - 1); @@ -725,10 +727,11 @@ MinerBlock.prototype.toRaw = function toRaw(writer) { MinerBlock.fromRaw = function fromRaw(data) { var p = new BufferReader(data); + var network = bcoin.network.fromMagic(p.readU32()); var tip = bcoin.chainentry.fromRaw(null, p); var version = p.readU32(); var bits = p.readU32(); - var address = p.readVarString('ascii'); + var address = bcoin.address.fromRaw(p.readVarBytes()); var coinbaseFlags = p.readVarString('utf8'); var witness = p.readU8() === 1; var count = p.readVarint(); @@ -738,7 +741,10 @@ MinerBlock.fromRaw = function fromRaw(data) { for (i = 0; i < count; i++) txs.push(bcoin.tx.fromRaw(p)); + tip.network = network; + return new MinerBlock({ + network: network, tip: tip, version: version, target: bits, diff --git a/lib/bcoin/network.js b/lib/bcoin/network.js index b4c8f747..a06e742f 100644 --- a/lib/bcoin/network.js +++ b/lib/bcoin/network.js @@ -9,9 +9,7 @@ var utils = require('./utils'); var assert = utils.assert; -var constants = require('./protocol/constants'); var networks = require('./protocol/network'); -var PolicyEstimator = require('./fees'); /** * Represents a network. @@ -57,8 +55,6 @@ function Network(options) { this.maxRate = options.maxRate; this.selfConnect = options.selfConnect; this.requestMempool = options.requestMempool; - - this.fees = new PolicyEstimator(constants.tx.MIN_RELAY, this); } /** diff --git a/lib/bcoin/node.js b/lib/bcoin/node.js index 68d59e4c..53270c11 100644 --- a/lib/bcoin/node.js +++ b/lib/bcoin/node.js @@ -10,6 +10,7 @@ var bcoin = require('./env'); var AsyncObject = require('./async'); var utils = require('./utils'); +var Profiler; /** * Base class from which every other @@ -26,22 +27,213 @@ function Node(options) { AsyncObject.call(this); - if (!options) - options = {}; + options = this._parseOptions(options); this.options = options; this.network = bcoin.network.get(options.network); + this.prefix = options.prefix; + this.logger = options.logger; + this.db = options.db; + this.profiler = null; this.mempool = null; this.pool = null; this.chain = null; + this.fees = null; this.miner = null; this.walletdb = null; this.wallet = null; + + this._bound = []; + + if (!this.logger) { + this.logger = new bcoin.logger({ + level: options.logLevel || 'none', + file: options.logFile + }); + } + + if (options.profile && !utils.isBrowser) { + Profiler = require('./prof' + 'iler'); + this.profiler = new Profiler(this.prefix); + } + + this.__init(); } utils.inherits(Node, AsyncObject); +/** + * Initialize node. + * @private + */ + +Node.prototype.__init = function __init() { + var self = this; + this.on('preopen', function() { + self._onOpen(); + }); + this.on('close', function() { + self._onClose(); + }); +}; + +/** + * Open node. Bind all events. + * @private + */ + +Node.prototype._onOpen = function _onOpen() { + var self = this; + + this.logger.open(); + + this._bind(bcoin.time, 'offset', function(offset) { + self.logger.info('Time offset: %d (%d minutes).', offset, offset / 60 | 0); + }); + + this._bind(bcoin.time, 'sample', function(sample, total) { + self.logger.debug('Added time data: samples=%d, offset=%d (%d minutes).', + total, sample, sample / 60 | 0); + }); + + this._bind(bcoin.time, 'mismatch', function() { + self.logger.warning('Please make sure your system clock is correct!'); + }); + + this._bind(bcoin.workerPool, 'spawn', function(child) { + self.logger.info('Spawning worker process: %d.', child.id); + }); + + this._bind(bcoin.workerPool, 'exit', function(code, child) { + self.logger.warning('Worker %d exited: %s.', child.id, code); + }); + + this._bind(bcoin.workerPool, 'error', function(err, child) { + if (child) { + self.logger.error('Worker %d error: %s', child.id, err.message); + return; + } + self.emit('error', err); + }); +}; + +/** + * Close node. Unbind all events. + * @private + */ + +Node.prototype._onClose = function _onClose() { + var i, bound; + + this.logger.close(); + + for (i = 0; i < this._bound.length; i++) { + bound = this._bound[i]; + bound[0].removeListener(bound[1], bound[2]); + } + + this._bound.length = 0; +}; + +/** + * Bind to an event on `obj`, save listener for removal. + * @private + * @param {EventEmitter} obj + * @param {String} event + * @param {Function} listener + */ + +Node.prototype._bind = function _bind(obj, event, listener) { + this._bound.push([obj, event, listener]); + obj.on(event, listener); +}; + +/** + * Emit and log an error. + * @private + * @param {Error} err + */ + +Node.prototype._error = function _error(err) { + this.logger.error(err); + this.emit('error', err); +}; + +/** + * Parse options object. + * @private + * @param {Object} options + * @returns {Object} + */ + +Node.prototype._parseOptions = function _parseOptions(options) { + if (!options) + options = {}; + + options = utils.merge({}, options); + + if (process.env.BCOIN_PREFIX != null) + options.prefix = process.env.BCOIN_PREFIX; + + if (process.env.BCOIN_DB != null) + options.db = process.env.BCOIN_DB; + + if (process.env.BCOIN_LOGLEVEL != null) + options.logLevel = process.env.BCOIN_LOGLEVEL; + + if (process.env.BCOIN_LOGFILE != null) { + if (process.env.BCOIN_LOGFILE === '0' + || process.env.BCOIN_LOGFILE === '1') { + options.logFile = +process.env.BCOIN_LOGFILE === 1; + } else { + options.logFile = process.env.BCOIN_LOGFILE; + } + } + + if (process.env.BCOIN_SEED != null) + options.preferredSeed = process.env.BCOIN_SEED; + + if (process.env.BCOIN_PROFILE != null) + options.profile = +process.env.BCOIN_PROFILE === 1; + + if (!options.prefix) + options.prefix = utils.HOME + '/.bcoin'; + + if (!options.db) + options.db = 'memory'; + + options.prefix = utils.normalize(options.prefix); + + if (options.logFile && typeof options.logFile !== 'string') { + options.logFile = options.prefix; + if (options.network !== 'main') + options.logFile += '/' + options.network; + options.logFile += '/debug.log'; + } + + options.logFile = options.logFile + ? utils.normalize(options.logFile) + : null; + + return options; +}; + +/** + * Create a file path from a name + * as well as the node's prefix. + * @param {String} name + * @returns {String} + */ + +Node.prototype.location = function location(name) { + var path = this.prefix; + if (this.network.type !== 'main') + path += '/' + this.network.type; + path += '/' + name; + return path; +}; + /* * Expose */ diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 389bcd93..891c0916 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -76,6 +76,7 @@ function Peer(pool, options) { this.options = options; this.pool = pool; + this.logger = pool.logger; this.socket = null; this.host = null; this.port = 0; @@ -236,7 +237,7 @@ Peer.prototype._onConnect = function _onConnect() { // Wait for _their_ version. if (!self.version) { - bcoin.debug( + self.logger.debug( 'Peer sent a verack without a version (%s).', self.hostname); self.request('version', callee); @@ -287,7 +288,7 @@ Peer.prototype._onConnect = function _onConnect() { self.sendMempool(); } - bcoin.debug('Received verack (%s).', self.hostname); + self.logger.debug('Received verack (%s).', self.hostname); // Finally we can let the pool know // that this peer is ready to go. @@ -321,19 +322,19 @@ Peer.prototype.createSocket = function createSocket(port, host) { if (this._createSocket) { socket = this._createSocket(port, host); } else { - if (bcoin.isBrowser) { + if (utils.isBrowser) { proxy = require('../../browser/proxysocket'); - socket = proxy.connect(bcoin.proxyServer, port, host); + socket = proxy.connect(this.pool.proxyServer, port, host); } else { net = require('n' + 'et'); socket = net.connect(port, host); } } - bcoin.debug('Connecting to %s.', hostname); + this.logger.debug('Connecting to %s.', hostname); socket.once('connect', function() { - bcoin.debug('Connected to %s.', hostname); + self.logger.info('Connected to %s.', hostname); }); this._connectTimeout = setTimeout(function() { @@ -413,7 +414,7 @@ Peer.prototype.sendInv = function sendInv(items) { if (items.length === 0) return; - bcoin.debug('Serving %d inv items to %s.', + this.logger.debug('Serving %d inv items to %s.', items.length, this.hostname); for (i = 0; i < items.length; i += 50000) { @@ -442,7 +443,7 @@ Peer.prototype.sendHeaders = function sendHeaders(items) { if (items.length === 0) return; - bcoin.debug('Serving %d headers to %s.', + this.logger.debug('Serving %d headers to %s.', items.length, this.hostname); for (i = 0; i < items.length; i += 2000) { @@ -485,7 +486,7 @@ Peer.prototype.sendPing = function sendPing() { } if (this.challenge) { - bcoin.debug('Peer has not responded to ping (%s).', this.hostname); + this.logger.debug('Peer has not responded to ping (%s).', this.hostname); return; } @@ -792,7 +793,7 @@ Peer.prototype._onPacket = function onPacket(packet) { this.fire(cmd, payload); break; default: - bcoin.debug('Unknown packet: %s.', cmd); + this.logger.warning('Unknown packet: %s.', cmd); this.fire(cmd, payload); break; } @@ -876,7 +877,7 @@ Peer.prototype._handleFilterClear = function _handleFilterClear(payload) { */ Peer.prototype._handleUTXOs = function _handleUTXOs(payload) { - bcoin.debug('Received %d utxos (%s).', + this.logger.debug('Received %d utxos (%s).', payload.coins.length, this.hostname); this.fire('utxos', payload); }; @@ -1333,7 +1334,7 @@ Peer.prototype._handleMempool = function _handleMempool() { for (i = 0; i < hashes.length; i++) items.push(new InvItem(constants.inv.TX, hashes[i])); - bcoin.debug('Sending mempool snapshot (%s).', self.hostname); + self.logger.debug('Sending mempool snapshot (%s).', self.hostname); self.sendInv(items); done(); @@ -1383,13 +1384,13 @@ Peer.prototype._handleGetData = function _handleGetData(items) { } if ((item.type & ~constants.WITNESS_MASK) !== entry.type) { - bcoin.debug( + self.logger.debug( 'Peer requested an existing item with the wrong type (%s).', this.hostname); continue; } - bcoin.debug( + this.logger.debug( 'Peer requested %s %s as a %s packet (%s).', entry.type === constants.inv.TX ? 'tx' : 'block', utils.revHex(entry.hash), @@ -1530,7 +1531,7 @@ Peer.prototype._handleGetData = function _handleGetData(items) { if (err) self.emit('error', err); - bcoin.debug( + self.logger.debug( 'Served %d items with getdata (notfound=%d) (%s).', items.length - notfound.length, notfound.length, @@ -1555,7 +1556,7 @@ Peer.prototype._handleAddr = function _handleAddr(addrs) { for (i = 0; i < addrs.length; i++) this.addrFilter.add(addrs[i].host, 'ascii'); - bcoin.debug( + this.logger.debug( 'Received %d addrs (hosts=%d, peers=%d) (%s).', addrs.length, this.pool.hosts.length, @@ -1586,17 +1587,17 @@ Peer.prototype._handlePong = function _handlePong(data) { var now = utils.ms(); if (!this.challenge) { - bcoin.debug('Peer sent an unsolicited pong (%s).', this.hostname); + this.logger.debug('Peer sent an unsolicited pong (%s).', this.hostname); return; } if (data.nonce.cmp(this.challenge) !== 0) { if (data.nonce.cmpn(0) === 0) { - bcoin.debug('Peer sent a zero nonce (%s).', this.hostname); + this.logger.debug('Peer sent a zero nonce (%s).', this.hostname); this.challenge = null; return; } - bcoin.debug('Peer sent the wrong nonce (%s).', this.hostname); + this.logger.debug('Peer sent the wrong nonce (%s).', this.hostname); return; } @@ -1606,7 +1607,7 @@ Peer.prototype._handlePong = function _handlePong(data) { this.minPing = now - this.lastPing; this.minPing = Math.min(this.minPing, now - this.lastPing); } else { - bcoin.debug('Timing mismatch (what?) (%s).', this.hostname); + this.logger.debug('Timing mismatch (what?) (%s).', this.hostname); } this.challenge = null; @@ -1645,7 +1646,7 @@ Peer.prototype._handleGetAddr = function _handleGetAddr() { if (items.length === 0) return; - bcoin.debug( + this.logger.debug( 'Sending %d addrs to peer (%s)', items.length, this.hostname); @@ -1691,7 +1692,7 @@ Peer.prototype._handleInv = function _handleInv(items) { this.emit('txs', txs); if (unknown != null) { - bcoin.debug( + this.logger.debug( 'Peer sent an unknown inv type: %d (%s).', unknown, this.hostname); } @@ -1767,11 +1768,11 @@ Peer.prototype.sendAlert = function sendAlert(alert) { Peer.prototype.sendGetHeaders = function sendGetHeaders(locator, stop) { var packet = new GetBlocksPacket(locator, stop); - bcoin.debug( + this.logger.debug( 'Requesting headers packet from peer with getheaders (%s).', this.hostname); - bcoin.debug('Height: %d, Hash: %s, Stop: %s', + this.logger.debug('Height: %d, Hash: %s, Stop: %s', locator && locator.length ? this.chain._getCachedHeight(locator[0]) : -1, locator && locator.length ? utils.revHex(locator[0]) : 0, stop ? utils.revHex(stop) : 0); @@ -1788,11 +1789,11 @@ Peer.prototype.sendGetHeaders = function sendGetHeaders(locator, stop) { Peer.prototype.sendGetBlocks = function getBlocks(locator, stop) { var packet = new GetBlocksPacket(locator, stop); - bcoin.debug( + this.logger.debug( 'Requesting inv packet from peer with getblocks (%s).', this.hostname); - bcoin.debug('Height: %d, Hash: %s, Stop: %s', + this.logger.debug('Height: %d, Hash: %s, Stop: %s', locator && locator.length ? this.chain._getCachedHeight(locator[0]) : -1, locator && locator.length ? utils.revHex(locator[0]) : 0, stop ? utils.revHex(stop) : 0); @@ -1805,7 +1806,7 @@ Peer.prototype.sendGetBlocks = function getBlocks(locator, stop) { */ Peer.prototype.sendMempool = function sendMempool() { - bcoin.debug( + this.logger.debug( 'Requesting inv packet from peer with mempool (%s).', this.hostname); @@ -1821,16 +1822,16 @@ Peer.prototype.sendReject = function sendReject(code, reason, obj) { var reject = RejectPacket.fromReason(code, reason, obj); if (obj) { - bcoin.debug('Rejecting %s %s (%s): ccode=%s reason=%s.', + this.logger.debug('Rejecting %s %s (%s): ccode=%s reason=%s.', reject.message, obj.rhash, this.hostname, code, reason); this.pool.rejects.add(obj.hash()); } else { - bcoin.debug('Rejecting packet from %s: ccode=%s reason=%s.', + this.logger.debug('Rejecting packet from %s: ccode=%s reason=%s.', this.hostname, code, reason); } - bcoin.debug( + this.logger.debug( 'Sending reject packet to peer (%s).', this.hostname); @@ -1894,7 +1895,7 @@ Peer.prototype.resolveOrphan = function resolveOrphan(tip, orphan, callback) { // Was probably resolved. if (!root) { - bcoin.debug('Orphan root was already resolved.'); + self.logger.debug('Orphan root was already resolved.'); return callback(); } diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 9f8f0d66..c385b0e0 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -92,6 +92,7 @@ function Pool(options) { this.options = options; this.chain = options.chain; + this.logger = options.logger || this.chain.logger; this.mempool = options.mempool; assert(this.chain, 'Pool requires a blockchain.'); @@ -106,9 +107,9 @@ function Pool(options) { seeds = options.seeds || this.network.seeds; - if (process.env.BCOIN_SEED) { + if (options.preferredSeed) { seeds = seeds.slice(); - seeds.unshift(process.env.BCOIN_SEED); + seeds.unshift(options.preferredSeed); } this.seeds = []; @@ -135,6 +136,7 @@ function Pool(options) { this.uid = 0; this._createServer = options.createServer; this.locker = new bcoin.locker(this); + this.proxyServer = options.proxyServer; this.syncing = false; this.synced = false; @@ -273,7 +275,7 @@ Pool.prototype._init = function _init() { self.synced = true; self.emit('full'); - bcoin.debug('Chain is fully synced (height=%d).', self.chain.height); + self.logger.info('Chain is fully synced (height=%d).', self.chain.height); }); }; @@ -287,11 +289,11 @@ Pool.prototype._open = function open(callback) { var self = this; this.getIP(function(err, ip) { if (err) - bcoin.error(err); + self.logger.error(err); if (ip) { self.address.host = ip; - bcoin.debug('External IP found: %s.', ip); + self.logger.info('External IP found: %s.', ip); } if (self.mempool) @@ -404,7 +406,7 @@ Pool.prototype.listen = function listen(callback) { if (this._createServer) { this.server = this._createServer(); } else { - if (bcoin.isBrowser) + if (utils.isBrowser) return utils.nextTick(callback); net = require('n' + 'et'); this.server = new net.Server(); @@ -414,7 +416,7 @@ Pool.prototype.listen = function listen(callback) { var hostname, host; if (!socket.remoteAddress) { - bcoin.debug('Ignoring disconnected leech.'); + self.logger.debug('Ignoring disconnected leech.'); socket.destroy(); return; } @@ -423,14 +425,14 @@ Pool.prototype.listen = function listen(callback) { if (self.peers.leeches.length >= self.maxLeeches) { hostname = IP.hostname(host, socket.remotePort); - bcoin.debug('Ignoring leech: too many leeches (%s).', hostname); + self.logger.debug('Ignoring leech: too many leeches (%s).', hostname); socket.destroy(); return; } if (self.isMisbehaving(host)) { hostname = IP.hostname(host, socket.remotePort); - bcoin.debug('Ignoring misbehaving leech (%s).', hostname); + self.logger.debug('Ignoring misbehaving leech (%s).', hostname); socket.destroy(); return; } @@ -440,7 +442,7 @@ Pool.prototype.listen = function listen(callback) { this.server.on('listening', function() { var data = self.server.address(); - bcoin.debug( + self.logger.info( 'Bitcoin server listening on %s (port=%d).', data.address, data.port); }); @@ -456,7 +458,7 @@ Pool.prototype.listen = function listen(callback) { Pool.prototype.unlisten = function unlisten(callback) { callback = utils.ensure(callback); - if (bcoin.isBrowser) + if (utils.isBrowser) return utils.nextTick(callback); if (!this.server) @@ -486,7 +488,7 @@ Pool.prototype._startTimer = function _startTimer() { if (self.peers.load) { self.peers.load.destroy(); - bcoin.debug('Timer ran out. Finding new loader peer.'); + self.logger.debug('Timer ran out. Finding new loader peer.'); } } @@ -530,7 +532,7 @@ Pool.prototype._startInterval = function _startInterval() { if (self.chain.isBusy()) return self._startTimer(); - bcoin.debug('Warning: Stalling.'); + self.logger.warning('Stalling.'); } this._interval = setInterval(load, this.load.interval); @@ -573,7 +575,7 @@ Pool.prototype._addLoader = function _addLoader() { witness: this.options.witness }); - bcoin.debug('Added loader peer (%s).', peer.hostname); + this.logger.info('Added loader peer (%s).', peer.hostname); this.peers.load = peer; this.peers.all.push(peer); @@ -666,7 +668,7 @@ Pool.prototype._handleHeaders = function _handleHeaders(headers, peer, callback) if (!this.options.headers) return callback(); - bcoin.debug( + this.logger.debug( 'Received %s headers from peer (%s).', headers.length, peer.hostname); @@ -734,7 +736,7 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer, callback) { assert(!this.options.headers); - bcoin.debug( + this.logger.debug( 'Received %s block hashes from peer (%s).', hashes.length, peer.hostname); @@ -757,7 +759,7 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer, callback) { // should probably actually move to the // `exists` clause below if it is the last // hash. - bcoin.debug('Received known orphan hash (%s).', peer.hostname); + self.logger.debug('Received known orphan hash (%s).', peer.hostname); return peer.resolveOrphan(null, hash, next); } @@ -775,11 +777,11 @@ Pool.prototype._handleBlocks = function _handleBlocks(hashes, peer, callback) { if (exists && i === hashes.length - 1) { // Make sure we _actually_ have this block. if (!self.request.map[hash]) { - bcoin.debug('Received existing hash (%s).', peer.hostname); + self.logger.debug('Received existing hash (%s).', peer.hostname); return peer.getBlocks(hash, null, next); } // Otherwise, we're still requesting it. Ignore. - bcoin.debug('Received already-requested hash (%s).', peer.hostname); + self.logger.debug('Received requested hash (%s).', peer.hostname); } next(); @@ -850,7 +852,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { // us requesting them. if (!requested) { peer.invFilter.add(block.hash()); - bcoin.debug( + this.logger.warning( 'Received unrequested block: %s (%s).', block.rhash, peer.hostname); return utils.nextTick(callback); @@ -871,7 +873,7 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { peer.setMisbehavior(10); return callback(err); } - bcoin.debug('Peer sent an orphan block. Resolving.'); + self.logger.debug('Peer sent an orphan block. Resolving.'); return peer.resolveOrphan(null, block.hash('hex'), function(e) { self.scheduleRequests(peer); return callback(e || err); @@ -887,8 +889,8 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { self.emit('chain-progress', self.chain.getProgress(), peer); - if (self.chain.total % 20 === 0) { - bcoin.debug('Status:' + if (self.logger.level === 4 && self.chain.total % 20 === 0) { + self.logger.debug('Status:' + ' tip=%s ts=%s height=%d progress=%s' + ' blocks=%d orphans=%d active=%d' + ' queue=%d target=%s peers=%d' @@ -908,6 +910,13 @@ Pool.prototype._handleBlock = function _handleBlock(block, peer, callback) { self.chain.locker.jobs.length); } + if (self.chain.total % 2000 === 0) { + self.logger.info( + 'Received 2000 more blocks (height=%d, hash=%s).', + self.chain.height, + block.rhash); + } + return callback(); }); }; @@ -979,7 +988,7 @@ Pool.prototype._createPeer = function _createPeer(options) { self._stopTimer(); if (self.peers.regular.length === 0) { - bcoin.debug('%s %s %s', + self.logger.warning('%s %s %s', 'Could not connect to any peers.', 'Do you have a network connection?', 'Retrying in 5 seconds.'); @@ -1041,7 +1050,7 @@ Pool.prototype._createPeer = function _createPeer(options) { ? utils.revHex(payload.data) : null; - bcoin.debug( + self.logger.warning( 'Received reject (%s): msg=%s code=%s reason=%s data=%s.', peer.hostname, payload.message, @@ -1121,7 +1130,7 @@ Pool.prototype._createPeer = function _createPeer(options) { if (version.height > self.block.versionHeight) self.block.versionHeight = version.height; - bcoin.debug( + self.logger.info( 'Received version (%s): version=%d height=%d services=%s agent=%s', peer.hostname, version.version, @@ -1171,21 +1180,21 @@ Pool.prototype._handleAlert = function _handleAlert(alert, peer) { return; if (!alert.verify(this.network.alertKey)) { - bcoin.debug('Peer sent a phony alert packet (%s).', peer.hostname); + this.logger.warning('Peer sent a phony alert packet (%s).', peer.hostname); // Let's look at it because why not? - bcoin.debug(alert); + this.logger.debug(alert); peer.setMisbehavior(100); return; } if (now >= alert.relayUntil || now >= alert.expiration) { - bcoin.debug('Peer sent an expired alert packet (%s).', peer.hostname); - bcoin.debug(alert); + this.logger.warning('Peer sent an expired alert packet (%s).', peer.hostname); + this.logger.debug(alert); return; } - bcoin.debug('Received alert from peer (%s).', peer.hostname); - bcoin.debug(alert); + this.logger.warning('Received alert from peer (%s).', peer.hostname); + this.logger.warning(alert); this.sendAlert(alert); @@ -1215,7 +1224,7 @@ Pool.prototype._handleTX = function _handleTX(tx, peer, callback) { if (!this.mempool) this.tx.filter.add(tx.hash()); - bcoin.debug('Peer sent unrequested tx: %s (%s).', + this.logger.warning('Peer sent unrequested tx: %s (%s).', tx.rhash, peer.hostname); if (this.rejects.test(tx.hash())) { @@ -1268,7 +1277,7 @@ Pool.prototype._addLeech = function _addLeech(socket) { witness: false }); - bcoin.debug('Added leech peer (%s).', peer.hostname); + this.logger.info('Added leech peer (%s).', peer.hostname); this.peers.leeches.push(peer); this.peers.all.push(peer); @@ -1340,7 +1349,7 @@ Pool.prototype._removePeer = function _removePeer(peer) { delete this.peers.map[peer.host]; if (this.peers.load === peer) { - bcoin.debug('Removed loader peer (%s).', peer.hostname); + this.logger.info('Removed loader peer (%s).', peer.hostname); this.peers.load = null; } @@ -1454,7 +1463,7 @@ Pool.prototype.getData = function getData(peer, type, hash, options, callback) { if (type === self.tx.type) { if (peer.queue.tx.length === 0) { utils.nextTick(function() { - bcoin.debug( + self.logger.debug( 'Requesting %d/%d txs from peer with getdata (%s).', peer.queue.tx.length, self.request.activeTX, @@ -1510,7 +1519,7 @@ Pool.prototype.has = function has(type, hash, force, callback) { } else { // If we recently rejected this item. Ignore. if (self.rejects.test(hash, 'hex')) { - bcoin.debug('Peer sent a known reject: %s.', hash); + self.logger.debug('Peer sent a known reject: %s.', hash); return callback(null, true); } } @@ -1603,7 +1612,7 @@ Pool.prototype._sendRequests = function _sendRequests(peer) { for (i = 0; i < items.length; i++) items[i] = items[i].start(); - bcoin.debug( + this.logger.debug( 'Requesting %d/%d blocks from peer with getdata (%s).', items.length, this.request.activeBlocks, @@ -1771,7 +1780,7 @@ Pool.prototype.fillCoins = function fillCoins(tx, callback) { Pool.prototype.getLoaderHost = function getLoaderHost() { var host; - if (!this.connected && process.env.BCOIN_SEED) + if (!this.connected && this.options.preferredSeed) return this.seeds[0]; host = this.getRandom(this.seeds); @@ -1892,7 +1901,7 @@ Pool.prototype.setMisbehavior = function setMisbehavior(peer, score) { if (peer.banScore >= constants.BAN_SCORE) { this.peers.misbehaving[peer.host] = utils.now(); this.removeHost(peer.host); - bcoin.debug('Ban threshold exceeded (%s).', peer.host); + this.logger.debug('Ban threshold exceeded (%s).', peer.host); peer.destroy(); return true; } @@ -2035,7 +2044,8 @@ LoadRequest.prototype.destroy = function destroy() { LoadRequest.prototype._onTimeout = function _onTimeout() { if (this.type === this.pool.block.type && this.peer === this.pool.peers.load) { - bcoin.debug('Loader took too long serving a block. Finding a new one.'); + this.pool.logger.debug( + 'Loader took too long serving a block. Finding a new one.'); this.peer.destroy(); } return this.finish(new Error('Timed out.')); @@ -2273,7 +2283,7 @@ BroadcastItem.prototype.send = function send(peer, witness) { // They are an insta-ban from any bitcoind node. if (this.msg.isCoinbase()) { peer.write(peer.framer.notFound([this.toInv()])); - bcoin.debug('Failsafe: tried to relay a coinbase.'); + this.pool.logger.warning('Failsafe: tried to relay a coinbase.'); this.finish(new Error('Coinbase.')); return true; } diff --git a/lib/bcoin/profiler.js b/lib/bcoin/profiler.js index d1e2d470..069cdca3 100644 --- a/lib/bcoin/profiler.js +++ b/lib/bcoin/profiler.js @@ -7,13 +7,6 @@ 'use strict'; -/** - * @exports profiler - */ - -var profiler = exports; - -var bcoin = require('./env'); var utils = require('./utils'); var assert = utils.assert; var fs, v8profiler; @@ -22,7 +15,7 @@ function ensure() { if (v8profiler) return; - if (bcoin.profile && !bcoin.isBrowser) { + if (!utils.isBrowser) { v8profiler = require('v8-' + 'profiler'); fs = require('f' + 's'); } @@ -32,14 +25,15 @@ function ensure() { * A CPU profile. * @exports Profile * @constructor + * @param {String} prefix * @param {String?} name */ -function Profile(name) { - if (v8profiler && bcoin.profile) { +function Profile(prefix, name) { + if (v8profiler) { name = 'profile-' + (name ? name + '-' : '') + utils.ms(); - bcoin.debug('Starting CPU profile: %s', name); v8profiler.startProfiling(name, true); + this.prefix = prefix; this.name = name; this.profile = null; this.finished = false; @@ -93,8 +87,6 @@ Profile.prototype.save = function save(callback) { if (!this.profile) this.stop(); - bcoin.debug('Saving CPU profile: %s', this.name); - return this.profile['export'](function(err, result) { var file; @@ -105,12 +97,12 @@ Profile.prototype.save = function save(callback) { if (err) return callback(err); - file = bcoin.prefix + file = self.prefix + '/profiler/' + self.name + '.cpuprofile'; - bcoin.mkdir(file, true); + utils.mkdir(file, true); fs.writeFile(file, result, callback); }); @@ -120,14 +112,15 @@ Profile.prototype.save = function save(callback) { * Memory Snapshot * @exports Snapshot * @constructor + * @param {String} prefix * @param {String?} name */ -function Snapshot(name) { - if (v8profiler && bcoin.profile) { +function Snapshot(prefix, name) { + if (v8profiler) { name = 'snapshot-' + (name ? name + '-' : '') + utils.ms(); - bcoin.debug('Taking heap snapshot: %s', name); this.snapshot = v8profiler.takeSnapshot(name); + this.prefix = prefix; this.name = name; } } @@ -189,8 +182,6 @@ Snapshot.prototype.save = function save(callback) { assert(this.snapshot); - bcoin.debug('Saving heap snapshot: %s', this.name); - return this.snapshot['export'](function(err, result) { var file; @@ -200,26 +191,40 @@ Snapshot.prototype.save = function save(callback) { if (err) return callback(err); - file = bcoin.prefix + file = self.prefix + '/profiler/' + self.name + '.heapsnapshot'; - bcoin.mkdir(file, true); + utils.mkdir(file, true); fs.writeFile(file, result, callback); }); }; +/** + * Profiler + * @exports Profiler + * @constructor + * @param {String} prefix + */ + +function Profiler(prefix) { + if (!(this instanceof Profiler)) + return new Profiler(prefix); + + this.prefix = prefix || utils.HOME; +} + /** * Create a new CPU profile and begin profiling. * @param {String?} name * @returns {Profile} */ -profiler.startProfiling = function startProfiling(name) { +Profiler.prototype.startProfiling = function startProfiling(name) { ensure(); - return new Profile(name); + return new Profile(this.prefix, name); }; /** @@ -228,9 +233,9 @@ profiler.startProfiling = function startProfiling(name) { * @returns {Snapshot} */ -profiler.takeSnapshot = function takeSnapshot(name) { +Profiler.prototype.takeSnapshot = function takeSnapshot(name) { ensure(); - return new Snapshot(name); + return new Snapshot(this.prefix, name); }; /** @@ -239,28 +244,25 @@ profiler.takeSnapshot = function takeSnapshot(name) { * @param {Function?} callback */ -profiler.snapshot = function snapshot(name, callback) { - var snapshot, mem; - - ensure(); - +Profiler.prototype.snapshot = function snapshot(name, callback) { if (typeof name === 'function') { callback = name; name = null; } - if (bcoin.debugLogs && process.memoryUsage) { - mem = process.memoryUsage(); - bcoin.debug('Memory: rss=%dmb, js-heap=%d/%dmb native-heap=%dmb', - utils.mb(mem.rss), - utils.mb(mem.heapUsed), - utils.mb(mem.heapTotal), - utils.mb(mem.rss - mem.heapTotal)); + ensure(); + + if (!v8profiler) { + if (!callback) + return; + return utils.nextTick(callback); } - if (!v8profiler || !bcoin.profile) - return utils.asyncify(callback)(); - - snapshot = new Snapshot(name); - snapshot.save(callback); + this.takeSnapshot(name).save(callback); }; + +/* + * Expose + */ + +module.exports = Profiler; diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index eed1eca2..433e7885 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -344,7 +344,6 @@ Parser.prototype.parsePayload = function parsePayload(cmd, p) { case 'feefilter': return Parser.parseFeeFilter(p); default: - bcoin.debug('Unknown packet: %s', cmd); return p; } }; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index b43c3eb6..37a70a06 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -4425,6 +4425,9 @@ Script.checksig = function checksig(msg, sig, key, flags) { if (!(flags & constants.flags.VERIFY_LOW_S)) high = true; + if (bcoin.sigcache) + return bcoin.sigcache.verify(msg, sig.slice(0, -1), key, historical, high); + return bcoin.ec.verify(msg, sig.slice(0, -1), key, historical, high); }; diff --git a/lib/bcoin/sigcache.js b/lib/bcoin/sigcache.js index 47fbc225..6d0bd062 100644 --- a/lib/bcoin/sigcache.js +++ b/lib/bcoin/sigcache.js @@ -50,7 +50,7 @@ SigCache.prototype.add = function add(hash, sig, key) { this.valid[hash] = new SigCacheEntry(sig, key); - if (this.keys.length === this.size) { + if (this.keys.length >= this.size) { i = Math.floor(Math.random() * this.keys.length); k = this.keys[i]; delete this.valid[k]; diff --git a/lib/bcoin/spvnode.js b/lib/bcoin/spvnode.js index d7d94665..582ffe6d 100644 --- a/lib/bcoin/spvnode.js +++ b/lib/bcoin/spvnode.js @@ -42,6 +42,10 @@ function SPVNode(options) { this.chain = new bcoin.chain({ network: this.network, + logger: this.logger, + profiler: this.profiler, + db: this.db, + location: this.location('spvchain'), preload: this.options.preload, useCheckpoints: this.options.useCheckpoints, spv: true @@ -49,8 +53,11 @@ function SPVNode(options) { this.pool = new bcoin.pool({ network: this.network, + logger: this.logger, chain: this.chain, witness: this.network.witness, + proxyServer: this.options.proxyServer, + preferredSeed: this.options.preferredSeed, selfish: true, listen: false, spv: true @@ -58,12 +65,16 @@ function SPVNode(options) { this.walletdb = new bcoin.walletdb({ network: this.network, + logger: this.logger, + db: this.db, + location: this.location('walletdb'), verify: true }); if (!utils.isBrowser) { this.http = new bcoin.http.server({ network: this.network, + logger: this.logger, node: this, key: this.options.sslKey, cert: this.options.sslCert, @@ -87,20 +98,20 @@ SPVNode.prototype._init = function _init() { // Bind to errors this.pool.on('error', function(err) { - self.emit('error', err); + self._error(err); }); this.chain.on('error', function(err) { - self.emit('error', err); + self._error(err); }); this.walletdb.on('error', function(err) { - self.emit('error', err); + self._error(err); }); if (this.http) { this.http.on('error', function(err) { - self.emit('error', err); + self._error(err); }); } @@ -111,7 +122,7 @@ SPVNode.prototype._init = function _init() { this.on('tx', function(tx) { self.walletdb.addTX(tx, function(err) { if (err) - self.emit('error', err); + self._error(err); }); }); @@ -130,7 +141,7 @@ SPVNode.prototype._init = function _init() { this.chain.on('remove entry', function(entry) { self.walletdb.removeBlockSPV(entry, function(err) { if (err) - self.emit('error', err); + self._error(err); }); }); @@ -154,7 +165,7 @@ SPVNode.prototype._open = function open(callback) { if (err) return callback(err); - bcoin.debug('Node is loaded.'); + self.logger.info('Node is loaded.'); callback(); } @@ -190,7 +201,7 @@ SPVNode.prototype._open = function open(callback) { return next(err); if (hashes.length > 0) - bcoin.debug('Adding %d addresses to filter.', hashes.length); + self.logger.info('Adding %d addresses to filter.', hashes.length); for (i = 0; i < hashes.length; i++) self.pool.watch(hashes[i], 'hex'); @@ -205,7 +216,7 @@ SPVNode.prototype._open = function open(callback) { return next(err); if (txs.length > 0) - bcoin.debug('Rebroadcasting %d transactions.', txs.length); + self.logger.info('Rebroadcasting %d transactions.', txs.length); for (i = 0; i < txs.length; i++) self.pool.broadcast(txs[i]); @@ -226,7 +237,7 @@ SPVNode.prototype._open = function open(callback) { if (height === -1) return next(); - bcoin.debug('Rewinding chain to height %s.', height); + self.logger.info('Rewinding chain to height %s.', height); self.chain.reset(height, next); }); @@ -327,13 +338,14 @@ SPVNode.prototype.stopSync = function stopSync() { */ SPVNode.prototype.createWallet = function createWallet(options, callback) { + var self = this; this.walletdb.ensure(options, function(err, wallet) { if (err) return callback(err); assert(wallet); - bcoin.debug('Loaded wallet with id=%s address=%s', + self.logger.info('Loaded wallet with id=%s address=%s', wallet.id, wallet.getAddress()); return callback(null, wallet); diff --git a/lib/bcoin/timedata.js b/lib/bcoin/timedata.js index b3624d7c..a0ccefc1 100644 --- a/lib/bcoin/timedata.js +++ b/lib/bcoin/timedata.js @@ -7,8 +7,8 @@ 'use strict'; -var bcoin = require('./env'); var utils = require('./utils'); +var EventEmitter = require('events').EventEmitter; /** * An object which handles "adjusted time". This may not @@ -28,6 +28,8 @@ function TimeData(limit) { if (!(this instanceof TimeData)) return new TimeData(limit); + EventEmitter.call(this); + if (limit == null) limit = 200; @@ -38,6 +40,8 @@ function TimeData(limit) { this._checked = false; } +utils.inherits(TimeData, EventEmitter); + /** * Add time data. * @param {String} host @@ -58,8 +62,7 @@ TimeData.prototype.add = function add(host, time) { utils.binaryInsert(this.samples, sample, compare); - bcoin.debug('Added time data: samples=%d, offset=%d (%d minutes)', - this.samples.length, sample, sample / 60 | 0); + this.emit('sample', sample, this.samples.length); if (this.samples.length >= 5 && this.samples.length % 2 === 1) { median = this.samples[this.samples / 2 | 0]; @@ -79,13 +82,12 @@ TimeData.prototype.add = function add(host, time) { } if (!match) { this._checked = true; - bcoin.debug('Please make sure your system clock is correct!'); + this.emit('mismatch'); } } } - bcoin.debug('Time offset: %d (%d minutes)', - this.offset, this.offset / 60 | 0); + this.emit('offset', this.offset); } }; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index be681f32..bdab0ca9 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -678,10 +678,8 @@ TX.prototype.verifyInput = function verifyInput(index, flags) { assert(input, 'Input does not exist.'); - if (!input.coin) { - bcoin.debug('Coin is not available for verification.'); + if (!input.coin) return false; - } try { Script.verify( @@ -693,10 +691,8 @@ TX.prototype.verifyInput = function verifyInput(index, flags) { flags ); } catch (e) { - if (e.type === 'ScriptError') { - bcoin.debug('Script verification error: %s', e.message); + if (e.type === 'ScriptError') return false; - } throw e; } @@ -719,7 +715,7 @@ TX.prototype.verifyAsync = function verifyAsync(flags, callback) { flags = null; } - if (!bcoin.workerPool) { + if (!bcoin.useWorkers) { callback = utils.asyncify(callback); try { result = this.verify(flags); diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index 4b8a9188..cd577b48 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -57,6 +57,7 @@ function TXDB(db, options) { this.walletdb = db; this.db = db.db; + this.logger = db.logger; this.options = options; this.network = bcoin.network.get(options.network); this.busy = false; @@ -297,6 +298,12 @@ TXDB.prototype.add = function add(tx, callback, force) { if (!map) return callback(null, false); + self.logger.info( + 'Incoming transaction for %d accounts.', + map.outputs.length); + + self.logger.debug(map.outputs); + return self._add(tx, map, callback, force); }); }; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 8992ca03..8aabed6b 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -19,7 +19,7 @@ var assert = require('assert'); var bn = require('bn.js'); var util = require('util'); var Number, Math, Date; -var crypto, supersha, hash, aes; +var fs, crypto, supersha, hash, aes; /** * Reference to the global object. @@ -52,6 +52,7 @@ utils.isBrowser = || typeof window !== 'undefined'; if (!utils.isBrowser) { + fs = require('f' + 's'); crypto = require('cry' + 'pto'); try { supersha = require('super' + 'sha'); @@ -968,7 +969,7 @@ utils.format = function format(args, color) { * @param {...String} args */ -utils.print = function print() { +utils.log = function log() { var args = new Array(arguments.length); var i, msg; @@ -1004,7 +1005,7 @@ utils.error = function error() { msg = typeof args[0] !== 'object' ? utils.format(args, false) : args[0]; - console.log(msg); + console.error(msg); return; } @@ -2529,3 +2530,94 @@ utils.binaryRemove = function binaryRemove(items, item, compare) { items.splice(i, 1); return true; }; + +/** + * Normalize a path. + * @param {String} path + * @param {Boolean?} dirname + */ + +utils.normalize = function normalize(path, dirname) { + var parts; + + path = path.replace(/\\/g, '/'); + path = path.replace(/\/+$/, ''); + parts = path.split(/\/+/); + + if (dirname) + parts.pop(); + + return parts.join('/'); +}; + +/** + * Create a full directory structure. + * @param {String} path + */ + +utils.mkdirp = function mkdirp(path) { + var i, parts, stat; + + if (!fs) + return; + + path = path.replace(/\\/g, '/'); + path = path.replace(/\/+$/, ''); + parts = path.split(/\/+/); + path = ''; + + if (process.platform === 'win32') { + if (parts[0].indexOf(':') !== -1) + path = parts.shift() + '/'; + } + + if (parts[0].length === 0) { + parts.shift(); + path = '/'; + } + + for (i = 0; i < parts.length; i++) { + path += parts[i]; + + try { + stat = fs.statSync(path); + if (!stat.isDirectory()) + throw new Error('Could not create directory.'); + } catch (e) { + if (e.code === 'ENOENT') + fs.mkdirSync(path, 488 /* 0750 */); + else + throw e; + } + + path += '/'; + } +}; + +/** + * Ensure a directory. + * @param {String} path + * @param {Boolean?} dirname + */ + +utils.mkdir = function mkdir(path, dirname) { + if (utils.isBrowser) + return; + + path = utils.normalize(path, dirname); + + if (utils._paths[path]) + return; + + utils._paths[path] = true; + + return utils.mkdirp(path); +}; + +/** + * Cached mkdirp paths. + * @private + * @type {Object} + */ + +utils._paths = {}; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index c49d2657..4dadede7 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -521,6 +521,7 @@ Wallet.prototype.getPath = function getPath(address, callback) { Wallet.prototype.fill = function fill(tx, options, callback) { var self = this; + var rate; if (typeof options === 'function') { callback = options; @@ -550,6 +551,15 @@ Wallet.prototype.fill = function fill(tx, options, callback) { if (err) return callback(err); + rate = options.rate; + + if (rate == null) { + if (self.db.fees) + rate = self.db.fees.estimateFee(); + else + rate = self.network.getRate(); + } + try { tx.fill(coins, { selection: options.selection || 'age', @@ -560,9 +570,7 @@ Wallet.prototype.fill = function fill(tx, options, callback) { subtractFee: options.subtractFee, changeAddress: account.changeAddress.getAddress(), height: self.network.height, - rate: options.rate != null - ? options.rate - : self.network.fees.estimateFee(), + rate: rate, wallet: self, m: self.m, n: self.n @@ -2521,7 +2529,7 @@ MasterKey.prototype.decrypt = function decrypt(passphrase, callback) { var self = this; var unlock; - unlock = this.locker.lock(decrypt, [passphrase, callback]); + unlock = this.locker.lock(decrypt, [passphrase, callback]); if (!unlock) return; diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 89b9a7e5..ee0d9eb4 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -48,14 +48,14 @@ function WalletDB(options) { this.options = options; this.network = bcoin.network.get(options.network); + this.fees = options.fees; + this.logger = options.logger || bcoin.defaultLogger; // We need one read lock for `get` and `create`. // It will hold locks specific to wallet ids. this.readLock = new ReadLock(this); this.db = bcoin.ldb({ - network: this.network, - name: this.options.name || 'wallet', location: this.options.location, db: this.options.db, cacheSize: 8 << 20, @@ -523,6 +523,8 @@ WalletDB.prototype.create = function create(options, callback) { if (err) return callback(err); + self.logger.info('Created wallet %s.', wallet.id); + return callback(null, wallet); }); }); @@ -703,6 +705,11 @@ WalletDB.prototype.createAccount = function createAccount(options, callback) { if (err) return callback(err); + self.logger.info('Created account %s/%s/%d.', + account.id, + account.name, + account.accountIndex); + return callback(null, account); }); }); diff --git a/lib/bcoin/worker.js b/lib/bcoin/worker.js index 8713873a..ada8fb10 100644 --- a/lib/bcoin/worker.js +++ b/lib/bcoin/worker.js @@ -19,18 +19,12 @@ if (typeof importScripts !== 'undefined') { env = JSON.parse(event.data); - bcoin.set({ useWorkers: true }); - bcoin.network.set(env.BCOIN_WORKER_NETWORK); - bcoin.workers.listen(+env.BCOIN_WORKER_ID, { - debug: +env.BCOIN_WORKER_DEBUG === 1 - }); + bcoin.set(env.BCOIN_WORKER_NETWORK); + bcoin.workers.listen(); }; } else { env = process.env; bcoin = require('./env'); - bcoin.set({ useWorkers: true }); - bcoin.network.set(env.BCOIN_WORKER_NETWORK); - bcoin.workers.listen(+env.BCOIN_WORKER_ID, { - debug: +env.BCOIN_WORKER_DEBUG === 1 - }); + bcoin.set(env.BCOIN_WORKER_NETWORK); + bcoin.workers.listen(); } diff --git a/lib/bcoin/workers.js b/lib/bcoin/workers.js index cca5bde6..be1c88f8 100644 --- a/lib/bcoin/workers.js +++ b/lib/bcoin/workers.js @@ -27,7 +27,7 @@ var jobs; * @property {Number} size * @property {Number} timeout * @property {Object} children - * @property {Number} uid + * @property {Number} nonce */ function Workers(options) { @@ -36,11 +36,13 @@ function Workers(options) { EventEmitter.call(this); - this.uid = 0; + if (!options) + options = {}; + this.size = Math.max(1, options.size || Workers.CORES); this.timeout = options.timeout || 60000; - this.network = bcoin.network.get(options.network); this.children = []; + this.nonce = 0; } utils.inherits(Workers, EventEmitter); @@ -60,7 +62,9 @@ Workers.CORES = getCores(); Workers.children = []; /** - * Cleanup all workers on exit. + * Destroy all workers. + * Used for cleaning up workers on exit. + * @private */ Workers.cleanup = function cleanup() { @@ -86,11 +90,13 @@ Workers._bindExit = function _bindExit() { function onExit(err) { Workers.cleanup(); + if (err) { - console.error(err.stack + ''); + utils.error(err.stack + ''); process.exit(1); return; } + process.exit(0); } @@ -124,55 +130,38 @@ Workers._bindExit = function _bindExit() { Workers.prototype.spawn = function spawn(id) { var self = this; - var i, child; + var child; - bcoin.debug('Spawning worker process: %d', id); - - child = new Worker(this, id); + child = new Worker(id); child.on('error', function(err) { - bcoin.debug('Worker %d error: %s', child.id, err.message); + self.emit('error', err, child); }); child.on('exit', function(code) { - bcoin.debug('Worker %d exited: %s', child.id, code); + self.emit('exit', code, child); if (self.children[child.id] === child) self.children[child.id] = null; - i = Workers.children.indexOf(child); - if (i !== -1) - Workers.children.splice(i, 1); }); - child.on('packet', function(job, body) { - if (body.name === 'event') { - child.emit.apply(child, body.items); - self.emit.apply(self, body.items); - return; - } - if (body.name === 'response') { - child.emit('response ' + job, body.items[0], body.items[1]); - return; - } - self.emit('error', new Error('Unknown packet: ' + body.name)); + child.on('event', function(items) { + self.emit('event', items, child); + self.emit.apply(self, items); }); - Workers.children.push(child); - - Workers._bindExit(); + this.emit('spawn', child); return child; }; /** * Allocate a new worker, will not go above `size` option - * and will automatically load balance the workers based - * on job ID. - * @param {Number} job + * and will automatically load balance the workers. * @returns {Worker} */ -Workers.prototype.alloc = function alloc(job) { - var id = job % this.size; +Workers.prototype.alloc = function alloc() { + var id = this.nonce++ % this.size; if (!this.children[id]) this.children[id] = this.spawn(id); return this.children[id]; @@ -228,20 +217,14 @@ Workers.prototype.destroy = function destroy() { */ Workers.prototype.execute = function execute(method, args, timeout, callback) { - var job = this.uid++; var child; - if (job > 0xffffffff) { - this.uid = 0; - job = this.uid++; - } - if (!timeout) timeout = this.timeout; - child = this.alloc(job); + child = this.alloc(); - child.execute(job, method, args, timeout, callback); + child.execute(method, args, timeout, callback); return child; }; @@ -287,33 +270,29 @@ Workers.prototype.scrypt = function scrypt(passwd, salt, N, r, p, len, callback) * Represents a worker. * @exports Worker * @constructor - * @param {Workers} pool - * @param {Number} id - Worker ID. - * @property {Number} id + * @param {Number?} id */ -function Worker(pool, id) { +function Worker(id) { var self = this; var penv, cp; if (!(this instanceof Worker)) - return new Worker(pool, id); + return new Worker(); EventEmitter.call(this); - this.id = id; - this.pool = pool; this.framer = new Framer(); this.parser = new Parser(); this.setMaxListeners(utils.MAX_SAFE_INTEGER); + this.uid = 0; + this.id = id != null ? id : -1; penv = { - BCOIN_WORKER_ID: id + '', - BCOIN_WORKER_NETWORK: this.pool.network.type, - BCOIN_WORKER_DEBUG: (bcoin.debugLogs || bcoin.debugFile) ? '1' : '0' + BCOIN_WORKER_NETWORK: bcoin.network.get().type }; - if (bcoin.isBrowser) { + if (utils.isBrowser) { this.child = new global.Worker('/bcoin-worker.js'); this.child.onerror = function onerror(err) { @@ -368,11 +347,6 @@ function Worker(pool, id) { this.child.stdout.on('data', function(data) { self.emit('data', data); }); - - this.child.stderr.setEncoding('utf8'); - this.child.stderr.on('data', function(data) { - bcoin.debug(data.trim()); - }); } this.on('data', function(data) { @@ -386,8 +360,63 @@ function Worker(pool, id) { this.parser.on('packet', function(job, body) { self.emit('packet', job, body); }); + + this._init(); } +/** + * Initialize worker. + * @private + */ + +Worker.prototype._init = function _init() { + var self = this; + + this.on('worker error', function(err) { + self.emit('error', toError(err)); + }); + + this.on('exit', function(code) { + var i = Workers.children.indexOf(self); + if (i !== -1) + Workers.children.splice(i, 1); + }); + + this.on('log', function(items) { + utils.log('Worker %d:', self.id); + utils.log.apply(utils, items); + }); + + this.on('packet', function(job, body) { + var err, result; + + if (body.name === 'event') { + self.emit.apply(self, body.items); + self.emit('event', body.items); + return; + } + + if (body.name === 'response') { + err = body.items[0]; + result = body.items[1]; + + if (err) + err = toError(err); + + self.emit('response ' + job, err, result); + + return; + } + + err = new Error('Unknown packet: ' + body.name); + self.emit('error', err); + }); + + Workers.children.push(this); + + Workers._bindExit(); +}; + /** * Send data to worker. * @param {Buffer} data @@ -395,7 +424,7 @@ function Worker(pool, id) { */ Worker.prototype.write = function write(data) { - if (bcoin.isBrowser) { + if (utils.isBrowser) { if (this.child.postMessage.length === 2) { data.__proto__ = Uint8Array.prototype; this.child.postMessage({ buf: data }, [data]); @@ -458,12 +487,17 @@ Worker.prototype.destroy = function destroy() { * the worker method specifies. */ -Worker.prototype.execute = function execute(job, method, args, timeout, callback) { +Worker.prototype.execute = function execute(method, args, timeout, callback) { var self = this; - var event = 'response ' + job; - var timer; + var job = this.uid++; + var event, timer; - assert(job <= 0xffffffff); + if (job > 0xffffffff) { + this.uid = 0; + job = this.uid++; + } + + event = 'response ' + job; function listener(err, result) { if (timer) { @@ -499,25 +533,22 @@ utils.inherits(Worker, EventEmitter); * Represents the master process. * @exports Master * @constructor - * @param {Number} id - Worker ID. * @param {Object?} options - * @property {Number} id */ -function Master(id, options) { +function Master(options) { var self = this; if (!(this instanceof Master)) - return new Master(id); + return new Master(); EventEmitter.call(this); - this.id = id; this.framer = new Framer(); this.parser = new Parser(); this.options = options || {}; - if (bcoin.isBrowser) { + if (utils.isBrowser) { global.onerror = function onerror(err) { self.emit('error', err); }; @@ -563,7 +594,7 @@ utils.inherits(Master, EventEmitter); */ Master.prototype.write = function write(data) { - if (bcoin.isBrowser) { + if (utils.isBrowser) { if (global.postMessage.length === 2) { data.__proto__ = Uint8Array.prototype; global.postMessage({ buf: data }, [data]); @@ -604,81 +635,53 @@ Master.prototype.sendEvent = function sendEvent() { return this.send(0, 'event', items); }; -/** - * Write a debug message, prefixing - * it with the worker ID. - * @param {...String} args - */ - -Master.prototype.debug = function debug() { - var i, args, msg; - - if (!this.options.debug) - return; - - args = new Array(arguments.length); - - for (i = 0; i < args.length; i++) - args[i] = arguments[i]; - - msg = utils.format(args, false); - msg = utils.format(['Worker %d: %s', this.id, msg], false); - - if (bcoin.isBrowser) { - console.error(msg); - return; - } - - process.stderr.write(msg + '\n'); -}; - -/** - * Write an error as a debug message. - * @param {Error} err - */ - -Master.prototype.error = function error(err) { - if (!err) - return; - - this.debug(err.message); -}; - /** * Destroy the worker. */ Master.prototype.destroy = function destroy() { - if (bcoin.isBrowser) + if (utils.isBrowser) return global.close(); return process.exit(0); }; +/** + * Write a message to stdout in the master process. + * @param {Object|String} obj + * @param {...String} args + */ + +Master.prototype.log = function log() { + var items = new Array(arguments.length); + var i; + + for (i = 0; i < items.length; i++) + items[i] = arguments[i]; + + this.sendEvent('log', items); +}; + /** * Listen for messages from master process (only if worker). - * @param {Number} id - Worker id. * @param {Object?} options * @returns {Master} */ -Master.listen = function listen(id, options) { - var master = new Master(id, options); - var debug = master.debug.bind(master); - var error = master.error.bind(master); +Master.listen = function listen(options) { + var master = new Master(options); - bcoin.debug = debug; - bcoin.error = error; - utils.print = debug; - utils.error = debug; + utils.log = master.log.bind(master); + utils.error = utils.log; master.on('error', function(err) { - bcoin.debug('Master error: %s', err.message); + master.sendEvent('worker error', fromError(err)); }); master.on('packet', function(job, body) { var result; if (body.name === 'event') { + master.emit('event', body.items); master.emit.apply(master, body.items); return; } @@ -686,16 +689,14 @@ Master.listen = function listen(id, options) { try { result = jobs[body.name].apply(jobs, body.items); } catch (e) { - bcoin.error(e); - return master.send(job, 'response', [{ - message: e.message, - stack: e.stack + '' - }]); + return master.send(job, 'response', [fromError(e)]); } return master.send(job, 'response', [null, result]); }); + bcoin.master = master; + return master; }; @@ -727,13 +728,7 @@ jobs.verify = function verify(tx, flags) { jobs.mine = function mine(attempt) { attempt.on('status', function(stat) { - bcoin.debug( - 'Miner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s', - stat.hashrate / 1000 | 0, - stat.hashes, - stat.target, - stat.height, - stat.best); + bcoin.master.sendEvent('status', stat); }); return attempt.mineSync(); }; @@ -794,7 +789,8 @@ Framer.prototype.body = function body(name, items) { return p.render(); }; -Framer.item = function _item(item, p) { +Framer.item = function _item(item, writer) { + var p = BufferWriter(writer); var i, keys; switch (typeof item) { @@ -834,7 +830,7 @@ Framer.item = function _item(item, p) { p.writeU8(45); item.toRaw(p); } else if (bn.isBN(item)) { - p.writeU8(50); + p.writeU8(10); p.writeVarBytes(item.toArrayLike(Buffer)); } else if (Buffer.isBuffer(item)) { p.writeU8(4); @@ -856,8 +852,13 @@ Framer.item = function _item(item, p) { } break; default: - assert(false, 'Bad type: ' + typeof item); + throw new Error('Bad type.'); } + + if (!writer) + p = p.render(); + + return p; }; /** @@ -880,58 +881,55 @@ function Parser() { utils.inherits(Parser, EventEmitter); Parser.prototype.feed = function feed(data) { - var chunk, header, body, rest; + var chunk, header, body; - this.pendingTotal += data.length; - this.pending.push(data); + while (data) { + this.pendingTotal += data.length; + this.pending.push(data); + data = null; - if (this.pendingTotal < this.waiting) - return; + if (this.pendingTotal < this.waiting) + break; - chunk = Buffer.concat(this.pending); + chunk = Buffer.concat(this.pending); - if (chunk.length > this.waiting) { - rest = chunk.slice(this.waiting); - chunk = chunk.slice(0, this.waiting); - } - - if (!this.header) { - this.header = this.parseHeader(chunk); - this.waiting = this.header.size; - this.pending.length = 0; - this.pendingTotal = 0; - - if (this.header.magic !== 0xdeadbeef) { - this.header = null; - this.waiting = 12; - this.emit('error', new Error('Bad magic number.')); - return; + if (chunk.length > this.waiting) { + data = chunk.slice(this.waiting); + chunk = chunk.slice(0, this.waiting); } - if (rest) - this.feed(rest); + if (!this.header) { + this.header = this.parseHeader(chunk); + this.waiting = this.header.size; + this.pending.length = 0; + this.pendingTotal = 0; - return; + if (this.header.magic !== 0xdeadbeef) { + this.header = null; + this.waiting = 12; + this.emit('error', new Error('Bad magic number.')); + continue; + } + + continue; + } + + header = this.header; + + this.pending.length = 0; + this.pendingTotal = 0; + this.waiting = 12; + this.header = null; + + try { + body = this.parseBody(chunk); + } catch (e) { + this.emit('error', e); + continue; + } + + this.emit('packet', header.job, body); } - - header = this.header; - - this.pending.length = 0; - this.pendingTotal = 0; - this.waiting = 12; - this.header = null; - - try { - body = this.parseBody(chunk); - } catch (e) { - this.emit('error', e); - return; - } - - this.emit('packet', header.job, body); - - if (rest) - this.feed(rest); }; Parser.prototype.parseHeader = function parseHeader(data) { @@ -957,7 +955,8 @@ Parser.prototype.parseBody = function parseBody(data) { }; }; -Parser.parseItem = function parseItem(p) { +Parser.parseItem = function parseItem(data) { + var p = BufferReader(data); var i, count, items; switch (p.readU8()) { @@ -983,6 +982,8 @@ Parser.parseItem = function parseItem(p) { for (i = 0; i < count; i++) items[p.readVarString('utf8')] = Parser.parseItem(p); return items; + case 10: + return new bn(p.readVarBytes()); case 40: return bcoin.block.fromRaw(p); case 41: @@ -995,17 +996,13 @@ Parser.parseItem = function parseItem(p) { return bcoin.mempoolentry.fromRaw(p); case 45: return bcoin.minerblock.fromRaw(p); - case 50: - return new bn(p.readVarBytes()); default: - assert(false, 'Bad type.'); + throw new Error('Bad type.'); } }; -/** - * Helper to retrieve number of cores. - * @memberof Workers - * @returns {Number} +/* + * Helpers */ function getCores() { @@ -1019,6 +1016,17 @@ function getCores() { return os.cpus().length; } +function toError(values) { + var err = new Error(values[0]); + err.stack = values[1]; + err.type = values[2]; + return err; +} + +function fromError(err) { + return [err.message, err.stack + '', err.type]; +} + /* * Expose */ diff --git a/package.json b/package.json index 153dc83b..86a388de 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ }, "repository": "git://github.com/bcoin-org/bcoin.git", "keywords": [ + "bcoin", "bitcoin", "blockchain", - "bcoin", "wallet" ], "author": "Fedor Indutny ", @@ -32,10 +32,10 @@ }, "dependencies": { "bn.js": "4.11.0", - "elliptic": "6.2.3", - "leveldown": "git://github.com/chjj/leveldown.git#staging" + "elliptic": "6.2.3" }, "optionalDependencies": { + "leveldown": "git://github.com/chjj/leveldown.git#staging", "secp256k1": "3.0.0", "socket.io": "1.4.5", "socket.io-client": "1.4.5" diff --git a/scripts/gen.js b/scripts/gen.js index eeb776c6..8c72f323 100644 --- a/scripts/gen.js +++ b/scripts/gen.js @@ -103,28 +103,28 @@ var segnet4 = createGenesisBlock({ nonce: 0 }); -utils.print(main); -utils.print(''); -utils.print(testnet); -utils.print(''); -utils.print(regtest); -utils.print(''); -utils.print(segnet3); -utils.print(''); -utils.print(segnet4); -utils.print(''); -utils.print(''); -utils.print('main hash: %s', main.rhash); -utils.print('main raw: %s', main.toRaw().toString('hex')); -utils.print(''); -utils.print('testnet hash: %s', testnet.rhash); -utils.print('testnet raw: %s', testnet.toRaw().toString('hex')); -utils.print(''); -utils.print('regtest hash: %s', regtest.rhash); -utils.print('regtest raw: %s', regtest.toRaw().toString('hex')); -utils.print(''); -utils.print('segnet3 hash: %s', segnet3.rhash); -utils.print('segnet3 raw: %s', segnet3.toRaw().toString('hex')); -utils.print(''); -utils.print('segnet4 hash: %s', segnet4.rhash); -utils.print('segnet4 raw: %s', segnet4.toRaw().toString('hex')); +utils.log(main); +utils.log(''); +utils.log(testnet); +utils.log(''); +utils.log(regtest); +utils.log(''); +utils.log(segnet3); +utils.log(''); +utils.log(segnet4); +utils.log(''); +utils.log(''); +utils.log('main hash: %s', main.rhash); +utils.log('main raw: %s', main.toRaw().toString('hex')); +utils.log(''); +utils.log('testnet hash: %s', testnet.rhash); +utils.log('testnet raw: %s', testnet.toRaw().toString('hex')); +utils.log(''); +utils.log('regtest hash: %s', regtest.rhash); +utils.log('regtest raw: %s', regtest.toRaw().toString('hex')); +utils.log(''); +utils.log('segnet3 hash: %s', segnet3.rhash); +utils.log('segnet3 raw: %s', segnet3.toRaw().toString('hex')); +utils.log(''); +utils.log('segnet4 hash: %s', segnet4.rhash); +utils.log('segnet4 raw: %s', segnet4.toRaw().toString('hex')); diff --git a/test/script-test.js b/test/script-test.js index 87bdac9b..8cc70efd 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -63,7 +63,6 @@ describe('Script', function() { opcodes.OP_5 ]); var stack = new Stack(); - utils.print(inputScript); inputScript.execute(stack); var res = prevOutScript.execute(stack); assert(res);