From 73664efcd03b40c1d468b8b16ddfd6337ed2cec6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 16 Nov 2017 20:32:26 -0800 Subject: [PATCH] bcoin: classify everything else. --- bin/cli | 1518 +++++++++++++++++++------------------ browser/proxysocket.js | 299 ++++---- browser/wsproxy.js | 397 +++++----- test/util/memwallet.js | 806 ++++++++++---------- test/util/node-context.js | 190 ++--- 5 files changed, 1607 insertions(+), 1603 deletions(-) diff --git a/bin/cli b/bin/cli index 3ea18d41..d99f8e52 100755 --- a/bin/cli +++ b/bin/cli @@ -23,798 +23,800 @@ const ANTIREPLAY = '' + '6a2e426974636f696e3a204120506565722d746f2d5065657' + '220456c656374726f6e696320436173682053797374656d'; -function CLI() { - this.config = new Config('bcoin', { - suffix: 'network', - fallback: 'main', - alias: { - 'n': 'network', - 'u': 'url', - 'uri': 'url', - 'k': 'api-key', - 's': 'ssl', - 'h': 'httphost', - 'p': 'httpport' - } - }); - - this.config.load({ - argv: true, - env: true - }); - - this.config.open('bcoin.conf'); - - this.argv = this.config.argv; - this.client = null; - this.wallet = null; -} - -CLI.prototype.log = function log(json) { - if (typeof json === 'string') - return console.log.apply(console, arguments); - return console.log(JSON.stringify(json, null, 2)); -}; - -CLI.prototype.getInfo = async function getInfo() { - const info = await this.client.getInfo(); - this.log(info); -}; - -CLI.prototype.getWallets = async function getWallets() { - const wallets = await this.client.getWallets(); - this.log(wallets); -}; - -CLI.prototype.createWallet = async function createWallet() { - const options = { - id: this.config.str([0, 'id']), - type: this.config.str('type'), - master: this.config.str('master'), - mnemonic: this.config.str('mnemonic'), - m: this.config.uint('m'), - n: this.config.uint('n'), - witness: this.config.bool('witness'), - passphrase: this.config.str('passphrase'), - watchOnly: this.config.has('key') ? true : this.config.bool('watch'), - accountKey: this.config.str('key') - }; - - const wallet = await this.client.createWallet(options); - - this.log(wallet); -}; - -CLI.prototype.getMaster = async function getMaster() { - const master = await this.wallet.getMaster(); - - this.log(master); -}; - -CLI.prototype.getKey = async function getKey() { - const address = this.config.str(0); - const key = await this.wallet.getKey(address); - - this.log(key); -}; - -CLI.prototype.getWIF = async function getWIF() { - const address = this.config.str(0); - const passphrase = this.config.str('passphrase'); - const key = await this.wallet.getWIF(address, passphrase); - - if (!key) { - this.log('Key not found.'); - return; - } - - this.log(key.privateKey); -}; - -CLI.prototype.addSharedKey = async function addSharedKey() { - const key = this.config.str(0); - const account = this.config.str('account'); - - await this.wallet.addSharedKey(account, key); - - this.log('Added key.'); -}; - -CLI.prototype.removeSharedKey = async function removeSharedKey() { - const key = this.config.str(0); - const account = this.config.str('account'); - - await this.wallet.removeSharedKey(account, key); - - this.log('Removed key.'); -}; - -CLI.prototype.getSharedKeys = async function getSharedKeys() { - const acct = this.config.str([0, 'account']); - const account = await this.wallet.getAccount(acct); - - if (!account) { - this.log('Account not found.'); - return; - } - - this.log(account.keys); -}; - -CLI.prototype.getAccount = async function getAccount() { - const acct = this.config.str([0, 'account']); - const account = await this.wallet.getAccount(acct); - - this.log(account); -}; - -CLI.prototype.createAccount = async function createAccount() { - const name = this.config.str([0, 'name']); - - const options = { - type: this.config.str('type'), - m: this.config.uint('m'), - n: this.config.uint('n'), - witness: this.config.bool('witness'), - accountKey: this.config.str('key') - }; - - const account = await this.wallet.createAccount(name, options); - - this.log(account); -}; - -CLI.prototype.createAddress = async function createAddress() { - const account = this.config.str([0, 'account']); - const addr = await this.wallet.createAddress(account); - - this.log(addr); -}; - -CLI.prototype.createChange = async function createChange() { - const account = this.config.str([0, 'account']); - const addr = await this.wallet.createChange(account); - - this.log(addr); -}; - -CLI.prototype.createNested = async function createNested() { - const account = this.config.str([0, 'account']); - const addr = await this.wallet.createNested(account); - - this.log(addr); -}; - -CLI.prototype.getAccounts = async function getAccounts() { - const accounts = await this.wallet.getAccounts(); - this.log(accounts); -}; - -CLI.prototype.getWallet = async function getWallet() { - const info = await this.wallet.getInfo(); - this.log(info); -}; - -CLI.prototype.getTX = async function getTX() { - const hash = this.config.str(0, ''); - - if (hash.length !== 64) { - const txs = await this.client.getTXByAddress(hash); - this.log(txs); - return; - } - - const tx = await this.client.getTX(hash); - - if (!tx) { - this.log('TX not found.'); - return; - } - - this.log(tx); -}; - -CLI.prototype.getBlock = async function getBlock() { - let hash = this.config.str(0, ''); - - if (hash.length !== 64) - hash = parseInt(hash, 10); - - const block = await this.client.getBlock(hash); - - if (!block) { - this.log('Block not found.'); - return; - } - - this.log(block); -}; - -CLI.prototype.getCoin = async function getCoin() { - const hash = this.config.str(0, ''); - const index = this.config.uint(1); - - if (hash.length !== 64) { - const coins = await this.client.getCoinsByAddress(hash); - this.log(coins); - return; - } - - const coin = await this.client.getCoin(hash, index); - - if (!coin) { - this.log('Coin not found.'); - return; - } - - this.log(coin); -}; - -CLI.prototype.getWalletHistory = async function getWalletHistory() { - const account = this.config.str('account'); - const txs = await this.wallet.getHistory(account); - - this.log(txs); -}; - -CLI.prototype.getWalletPending = async function getWalletPending() { - const account = this.config.str('account'); - const txs = await this.wallet.getPending(account); - - this.log(txs); -}; - -CLI.prototype.getWalletCoins = async function getWalletCoins() { - const account = this.config.str('account'); - const coins = await this.wallet.getCoins(account); - - this.log(coins); -}; - -CLI.prototype.listenWallet = async function listenWallet() { - await this.wallet.open(); - - this.wallet.on('tx', (details) => { - this.log('TX:'); - this.log(details); - }); - - this.wallet.on('confirmed', (details) => { - this.log('TX confirmed:'); - this.log(details); - }); - - this.wallet.on('unconfirmed', (details) => { - this.log('TX unconfirmed:'); - this.log(details); - }); - - this.wallet.on('conflict', (details) => { - this.log('TX conflict:'); - this.log(details); - }); - - this.wallet.on('address', (receive) => { - this.log('New addresses allocated:'); - this.log(receive); - }); - - this.wallet.on('balance', (balance) => { - this.log('Balance:'); - this.log(balance); - }); - - return await this.wallet.onDisconnect(); -}; - -CLI.prototype.getBalance = async function getBalance() { - const account = this.config.str('account'); - const balance = await this.wallet.getBalance(account); - - this.log(balance); -}; - -CLI.prototype.getMempool = async function getMempool() { - const txs = await this.client.getMempool(); - - this.log(txs); -}; - -CLI.prototype.sendTX = async function sendTX() { - const outputs = []; - - if (this.config.has('script')) { - outputs.push({ - script: this.config.str('script'), - value: this.config.ufixed([0, 'value'], 8) +class CLI { + constructor() { + this.config = new Config('bcoin', { + suffix: 'network', + fallback: 'main', + alias: { + 'n': 'network', + 'u': 'url', + 'uri': 'url', + 'k': 'api-key', + 's': 'ssl', + 'h': 'httphost', + 'p': 'httpport' + } }); - } else { - outputs.push({ - address: this.config.str([0, 'address']), - value: this.config.ufixed([1, 'value'], 8) + + this.config.load({ + argv: true, + env: true }); + + this.config.open('bcoin.conf'); + + this.argv = this.config.argv; + this.client = null; + this.wallet = null; } - if (this.config.bool('no-replay')) { - outputs.push({ - script: ANTIREPLAY, - value: 0 - }); + log(json) { + if (typeof json === 'string') + return console.log.apply(console, arguments); + return console.log(JSON.stringify(json, null, 2)); } - const options = { - account: this.config.str('account'), - passphrase: this.config.str('passphrase'), - outputs: outputs, - smart: this.config.bool('smart'), - rate: this.config.ufixed('rate', 8), - subtractFee: this.config.bool('subtract-fee') - }; + async getInfo() { + const info = await this.client.getInfo(); + this.log(info); + } - const tx = await this.wallet.send(options); + async getWallets() { + const wallets = await this.client.getWallets(); + this.log(wallets); + } - this.log(tx); -}; - -CLI.prototype.createTX = async function createTX() { - let output; - - if (this.config.has('script')) { - output = { - script: this.config.str('script'), - value: this.config.ufixed([0, 'value'], 8) - }; - } else { - output = { - address: this.config.str([0, 'address']), - value: this.config.ufixed([1, 'value'], 8) + async createWallet() { + const options = { + id: this.config.str([0, 'id']), + type: this.config.str('type'), + master: this.config.str('master'), + mnemonic: this.config.str('mnemonic'), + m: this.config.uint('m'), + n: this.config.uint('n'), + witness: this.config.bool('witness'), + passphrase: this.config.str('passphrase'), + watchOnly: this.config.has('key') ? true : this.config.bool('watch'), + accountKey: this.config.str('key') }; + + const wallet = await this.client.createWallet(options); + + this.log(wallet); } - const options = { - account: this.config.str('account'), - passphrase: this.config.str('passphrase'), - outputs: [output], - smart: this.config.bool('smart'), - rate: this.config.ufixed('rate', 8), - subtractFee: this.config.bool('subtract-fee') - }; + async getMaster() { + const master = await this.wallet.getMaster(); - const tx = await this.wallet.createTX(options); - - this.log(tx); -}; - -CLI.prototype.signTX = async function signTX() { - const passphrase = this.config.str('passphrase'); - const raw = this.config.str([0, 'tx']); - const tx = await this.wallet.sign(raw, { passphrase }); - - this.log(tx); -}; - -CLI.prototype.zapWallet = async function zapWallet() { - const age = this.config.uint([0, 'age'], 72 * 60 * 60); - - await this.wallet.zap(this.config.str('account'), age); - - this.log('Zapped!'); -}; - -CLI.prototype.broadcast = async function broadcast() { - const raw = this.config.str([0, 'tx']); - const tx = await this.client.broadcast(raw); - - this.log('Broadcasted:'); - this.log(tx); -}; - -CLI.prototype.viewTX = async function viewTX() { - const raw = this.config.str([0, 'tx']); - const tx = await this.wallet.fill(raw); - - this.log(tx); -}; - -CLI.prototype.getDetails = async function getDetails() { - const hash = this.config.str(0); - const details = await this.wallet.getTX(hash); - - this.log(details); -}; - -CLI.prototype.getWalletBlocks = async function getWalletBlocks() { - const blocks = await this.wallet.getBlocks(); - this.log(blocks); -}; - -CLI.prototype.getWalletBlock = async function getWalletBlock() { - const height = this.config.uint(0); - const block = await this.wallet.getBlock(height); - - this.log(block); -}; - -CLI.prototype.retoken = async function retoken() { - const passphrase = this.config.str('passphrase'); - const result = await this.wallet.retoken(passphrase); - - this.log(result); -}; - -CLI.prototype.rescan = async function rescan() { - const height = this.config.uint(0); - - await this.wallet.rescan(height); - - this.log('Rescanning...'); -}; - -CLI.prototype.reset = async function reset() { - let hash = this.config.str(0); - - if (hash.length !== 64) - hash = parseInt(hash, 10); - - await this.client.reset(hash); - - this.log('Chain has been reset.'); -}; - -CLI.prototype.resend = async function resend() { - await this.client.resend(); - - this.log('Resending...'); -}; - -CLI.prototype.resendWallet = async function resendWallet() { - await this.wallet.resend(); - - this.log('Resending...'); -}; - -CLI.prototype.backup = async function backup() { - const path = this.config.str(0); - - await this.client.backup(path); - - this.log('Backup complete.'); -}; - -CLI.prototype.importKey = async function importKey() { - const key = this.config.str(0); - const account = this.config.str('account'); - const passphrase = this.config.str('passphrase'); - - if (!key) - throw new Error('No key for import.'); - - if (key.length === 66 || key.length === 130) { - await this.wallet.importPublic(account, key); - this.log('Imported public key.'); - return; + this.log(master); } - await this.wallet.importPrivate(account, key, passphrase); + async getKey() { + const address = this.config.str(0); + const key = await this.wallet.getKey(address); - this.log('Imported private key.'); -}; - -CLI.prototype.importAddress = async function importAddress() { - const address = this.config.str(0); - const account = this.config.str('account'); - - await this.wallet.importAddress(account, address); - - this.log('Imported address.'); -}; - -CLI.prototype.lock = async function lock() { - await this.wallet.lock(); - - this.log('Locked.'); -}; - -CLI.prototype.unlock = async function unlock() { - const passphrase = this.config.str(0); - const timeout = this.config.uint(1); - - await this.wallet.unlock(passphrase, timeout); - - this.log('Unlocked.'); -}; - -CLI.prototype.rpc = async function rpc() { - const method = this.argv.shift(); - const params = []; - - for (const arg of this.argv) { - let param; - try { - param = JSON.parse(arg); - } catch (e) { - param = arg; - } - params.push(param); + this.log(key); } - let result; - try { - result = await this.client.rpc.execute(method, params); - } catch (e) { - if (e.type === 'RPCError') { - this.log(e.message); + async getWIF() { + const address = this.config.str(0); + const passphrase = this.config.str('passphrase'); + const key = await this.wallet.getWIF(address, passphrase); + + if (!key) { + this.log('Key not found.'); return; } - throw e; + + this.log(key.privateKey); } - this.log(result); -}; + async addSharedKey() { + const key = this.config.str(0); + const account = this.config.str('account'); -CLI.prototype.handleWallet = async function handleWallet() { - const network = this.config.str('network', 'main'); + await this.wallet.addSharedKey(account, key); - this.wallet = new WalletClient({ - url: this.config.str('url'), - apiKey: this.config.str('api-key'), - ssl: this.config.bool('ssl'), - host: this.config.str('http-host'), - port: this.config.uint('http-port') - || wports[network] - || wports.main, - id: this.config.str('id', 'primary'), - token: this.config.str('token') - }); - - switch (this.argv.shift()) { - case 'listen': - await this.listenWallet(); - break; - case 'get': - await this.getWallet(); - break; - case 'master': - await this.getMaster(); - break; - case 'shared': - if (this.argv[0] === 'add') { - this.argv.shift(); - await this.addSharedKey(); - break; - } - if (this.argv[0] === 'remove') { - this.argv.shift(); - await this.removeSharedKey(); - break; - } - if (this.argv[0] === 'list') - this.argv.shift(); - await this.getSharedKeys(); - break; - case 'balance': - await this.getBalance(); - break; - case 'history': - await this.getWalletHistory(); - break; - case 'pending': - await this.getWalletPending(); - break; - case 'coins': - await this.getWalletCoins(); - break; - case 'account': - if (this.argv[0] === 'list') { - this.argv.shift(); - await this.getAccounts(); - break; - } - if (this.argv[0] === 'create') { - this.argv.shift(); - await this.createAccount(); - break; - } - if (this.argv[0] === 'get') - this.argv.shift(); - await this.getAccount(); - break; - case 'address': - await this.createAddress(); - break; - case 'change': - await this.createChange(); - break; - case 'nested': - await this.createNested(); - break; - case 'retoken': - await this.retoken(); - break; - case 'sign': - await this.signTX(); - break; - case 'mktx': - await this.createTX(); - break; - case 'send': - await this.sendTX(); - break; - case 'zap': - await this.zapWallet(); - break; - case 'tx': - await this.getDetails(); - break; - case 'blocks': - await this.getWalletBlocks(); - break; - case 'block': - await this.getWalletBlock(); - break; - case 'view': - await this.viewTX(); - break; - case 'import': - await this.importKey(); - break; - case 'watch': - await this.importAddress(); - break; - case 'key': - await this.getKey(); - break; - case 'dump': - await this.getWIF(); - break; - case 'lock': - await this.lock(); - break; - case 'unlock': - await this.unlock(); - break; - case 'resend': - await this.resendWallet(); - break; - case 'rescan': - await this.rescan(); - break; - default: - this.log('Unrecognized command.'); - this.log('Commands:'); - this.log(' $ listen: Listen for events.'); - this.log(' $ get: View wallet.'); - this.log(' $ master: View wallet master key.'); - this.log(' $ shared add [xpubkey]: Add key to wallet.'); - this.log(' $ shared remove [xpubkey]: Remove key from wallet.'); - this.log(' $ balance: Get wallet balance.'); - this.log(' $ history: View TX history.'); - this.log(' $ pending: View pending TXs.'); - this.log(' $ coins: View wallet coins.'); - this.log(' $ account list: List account names.'); - this.log(' $ account create [account-name]: Create account.'); - this.log(' $ account get [account-name]: Get account details.'); - this.log(' $ address: Derive new address.'); - this.log(' $ change: Derive new change address.'); - this.log(' $ nested: Derive new nested address.'); - this.log(' $ retoken: Create new api key.'); - this.log(' $ send [address] [value]: Send transaction.'); - this.log(' $ mktx [address] [value]: Create transaction.'); - this.log(' $ sign [tx-hex]: Sign transaction.'); - this.log(' $ zap [age?]: Zap pending wallet TXs.'); - this.log(' $ tx [hash]: View transaction details.'); - this.log(' $ blocks: List wallet blocks.'); - this.log(' $ block [height]: View wallet block.'); - this.log(' $ view [tx-hex]: Parse and view transaction.'); - this.log(' $ import [wif|hex]: Import private or public key.'); - this.log(' $ watch [address]: Import an address.'); - this.log(' $ key [address]: Get wallet key by address.'); - this.log(' $ dump [address]: Get wallet key WIF by address.'); - this.log(' $ lock: Lock wallet.'); - this.log(' $ unlock [passphrase] [timeout?]: Unlock wallet.'); - this.log(' $ resend: Resend pending transactions.'); - this.log(' $ rescan [height]: Rescan for transactions.'); - this.log('Other Options:'); - this.log(' --passphrase [passphrase]: For signing & account creation.'); - this.log(' --account [account-name]: Account name.'); - break; + this.log('Added key.'); } -}; -CLI.prototype.handleNode = async function handleNode() { - const network = this.config.str('network', 'main'); + async removeSharedKey() { + const key = this.config.str(0); + const account = this.config.str('account'); - this.client = new NodeClient({ - url: this.config.str('url'), - apiKey: this.config.str('api-key'), - ssl: this.config.bool('ssl'), - host: this.config.str('http-host'), - port: this.config.uint('http-port') - || nports[network] - || nports.main - }); + await this.wallet.removeSharedKey(account, key); - switch (this.argv.shift()) { - case 'info': - await this.getInfo(); - break; - case 'wallets': - await this.getWallets(); - break; - case 'mkwallet': - await this.createWallet(); - break; - case 'broadcast': - await this.broadcast(); - break; - case 'mempool': - await this.getMempool(); - break; - case 'tx': - await this.getTX(); - break; - case 'coin': - await this.getCoin(); - break; - case 'block': - await this.getBlock(); - break; - case 'reset': - await this.reset(); - break; - case 'resend': - await this.resend(); - break; - case 'backup': - await this.backup(); - break; - case 'rpc': - await this.rpc(); - break; - default: - this.log('Unrecognized command.'); - this.log('Commands:'); - this.log(' $ info: Get server info.'); - this.log(' $ wallets: List all wallets.'); - this.log(' $ wallet create [id]: Create wallet.'); - this.log(' $ broadcast [tx-hex]: Broadcast transaction.'); - this.log(' $ mempool: Get mempool snapshot.'); - this.log(' $ tx [hash/address]: View transactions.'); - this.log(' $ coin [hash+index/address]: View coins.'); - this.log(' $ block [hash/height]: View block.'); - this.log(' $ reset [height/hash]: Reset chain to desired block.'); - this.log(' $ resend: Resend pending transactions.'); - this.log(' $ backup [path]: Backup the wallet db.'); - this.log(' $ rpc [command] [args]: Execute RPC command.'); - break; + this.log('Removed key.'); } -}; -CLI.prototype.open = async function open() { - switch (this.argv[0]) { - case 'w': - case 'wallet': - this.argv.shift(); - if (this.argv[0] === 'create') { - this.argv[0] = 'mkwallet'; + async getSharedKeys() { + const acct = this.config.str([0, 'account']); + const account = await this.wallet.getAccount(acct); + + if (!account) { + this.log('Account not found.'); + return; + } + + this.log(account.keys); + } + + async getAccount() { + const acct = this.config.str([0, 'account']); + const account = await this.wallet.getAccount(acct); + + this.log(account); + } + + async createAccount() { + const name = this.config.str([0, 'name']); + + const options = { + type: this.config.str('type'), + m: this.config.uint('m'), + n: this.config.uint('n'), + witness: this.config.bool('witness'), + accountKey: this.config.str('key') + }; + + const account = await this.wallet.createAccount(name, options); + + this.log(account); + } + + async createAddress() { + const account = this.config.str([0, 'account']); + const addr = await this.wallet.createAddress(account); + + this.log(addr); + } + + async createChange() { + const account = this.config.str([0, 'account']); + const addr = await this.wallet.createChange(account); + + this.log(addr); + } + + async createNested() { + const account = this.config.str([0, 'account']); + const addr = await this.wallet.createNested(account); + + this.log(addr); + } + + async getAccounts() { + const accounts = await this.wallet.getAccounts(); + this.log(accounts); + } + + async getWallet() { + const info = await this.wallet.getInfo(); + this.log(info); + } + + async getTX() { + const hash = this.config.str(0, ''); + + if (hash.length !== 64) { + const txs = await this.client.getTXByAddress(hash); + this.log(txs); + return; + } + + const tx = await this.client.getTX(hash); + + if (!tx) { + this.log('TX not found.'); + return; + } + + this.log(tx); + } + + async getBlock() { + let hash = this.config.str(0, ''); + + if (hash.length !== 64) + hash = parseInt(hash, 10); + + const block = await this.client.getBlock(hash); + + if (!block) { + this.log('Block not found.'); + return; + } + + this.log(block); + } + + async getCoin() { + const hash = this.config.str(0, ''); + const index = this.config.uint(1); + + if (hash.length !== 64) { + const coins = await this.client.getCoinsByAddress(hash); + this.log(coins); + return; + } + + const coin = await this.client.getCoin(hash, index); + + if (!coin) { + this.log('Coin not found.'); + return; + } + + this.log(coin); + } + + async getWalletHistory() { + const account = this.config.str('account'); + const txs = await this.wallet.getHistory(account); + + this.log(txs); + } + + async getWalletPending() { + const account = this.config.str('account'); + const txs = await this.wallet.getPending(account); + + this.log(txs); + } + + async getWalletCoins() { + const account = this.config.str('account'); + const coins = await this.wallet.getCoins(account); + + this.log(coins); + } + + async listenWallet() { + await this.wallet.open(); + + this.wallet.on('tx', (details) => { + this.log('TX:'); + this.log(details); + }); + + this.wallet.on('confirmed', (details) => { + this.log('TX confirmed:'); + this.log(details); + }); + + this.wallet.on('unconfirmed', (details) => { + this.log('TX unconfirmed:'); + this.log(details); + }); + + this.wallet.on('conflict', (details) => { + this.log('TX conflict:'); + this.log(details); + }); + + this.wallet.on('address', (receive) => { + this.log('New addresses allocated:'); + this.log(receive); + }); + + this.wallet.on('balance', (balance) => { + this.log('Balance:'); + this.log(balance); + }); + + return await this.wallet.onDisconnect(); + } + + async getBalance() { + const account = this.config.str('account'); + const balance = await this.wallet.getBalance(account); + + this.log(balance); + } + + async getMempool() { + const txs = await this.client.getMempool(); + + this.log(txs); + } + + async sendTX() { + const outputs = []; + + if (this.config.has('script')) { + outputs.push({ + script: this.config.str('script'), + value: this.config.ufixed([0, 'value'], 8) + }); + } else { + outputs.push({ + address: this.config.str([0, 'address']), + value: this.config.ufixed([1, 'value'], 8) + }); + } + + if (this.config.bool('no-replay')) { + outputs.push({ + script: ANTIREPLAY, + value: 0 + }); + } + + const options = { + account: this.config.str('account'), + passphrase: this.config.str('passphrase'), + outputs: outputs, + smart: this.config.bool('smart'), + rate: this.config.ufixed('rate', 8), + subtractFee: this.config.bool('subtract-fee') + }; + + const tx = await this.wallet.send(options); + + this.log(tx); + } + + async createTX() { + let output; + + if (this.config.has('script')) { + output = { + script: this.config.str('script'), + value: this.config.ufixed([0, 'value'], 8) + }; + } else { + output = { + address: this.config.str([0, 'address']), + value: this.config.ufixed([1, 'value'], 8) + }; + } + + const options = { + account: this.config.str('account'), + passphrase: this.config.str('passphrase'), + outputs: [output], + smart: this.config.bool('smart'), + rate: this.config.ufixed('rate', 8), + subtractFee: this.config.bool('subtract-fee') + }; + + const tx = await this.wallet.createTX(options); + + this.log(tx); + } + + async signTX() { + const passphrase = this.config.str('passphrase'); + const raw = this.config.str([0, 'tx']); + const tx = await this.wallet.sign(raw, { passphrase }); + + this.log(tx); + } + + async zapWallet() { + const age = this.config.uint([0, 'age'], 72 * 60 * 60); + + await this.wallet.zap(this.config.str('account'), age); + + this.log('Zapped!'); + } + + async broadcast() { + const raw = this.config.str([0, 'tx']); + const tx = await this.client.broadcast(raw); + + this.log('Broadcasted:'); + this.log(tx); + } + + async viewTX() { + const raw = this.config.str([0, 'tx']); + const tx = await this.wallet.fill(raw); + + this.log(tx); + } + + async getDetails() { + const hash = this.config.str(0); + const details = await this.wallet.getTX(hash); + + this.log(details); + } + + async getWalletBlocks() { + const blocks = await this.wallet.getBlocks(); + this.log(blocks); + } + + async getWalletBlock() { + const height = this.config.uint(0); + const block = await this.wallet.getBlock(height); + + this.log(block); + } + + async retoken() { + const passphrase = this.config.str('passphrase'); + const result = await this.wallet.retoken(passphrase); + + this.log(result); + } + + async rescan() { + const height = this.config.uint(0); + + await this.wallet.rescan(height); + + this.log('Rescanning...'); + } + + async reset() { + let hash = this.config.str(0); + + if (hash.length !== 64) + hash = parseInt(hash, 10); + + await this.client.reset(hash); + + this.log('Chain has been reset.'); + } + + async resend() { + await this.client.resend(); + + this.log('Resending...'); + } + + async resendWallet() { + await this.wallet.resend(); + + this.log('Resending...'); + } + + async backup() { + const path = this.config.str(0); + + await this.client.backup(path); + + this.log('Backup complete.'); + } + + async importKey() { + const key = this.config.str(0); + const account = this.config.str('account'); + const passphrase = this.config.str('passphrase'); + + if (!key) + throw new Error('No key for import.'); + + if (key.length === 66 || key.length === 130) { + await this.wallet.importPublic(account, key); + this.log('Imported public key.'); + return; + } + + await this.wallet.importPrivate(account, key, passphrase); + + this.log('Imported private key.'); + } + + async importAddress() { + const address = this.config.str(0); + const account = this.config.str('account'); + + await this.wallet.importAddress(account, address); + + this.log('Imported address.'); + } + + async lock() { + await this.wallet.lock(); + + this.log('Locked.'); + } + + async unlock() { + const passphrase = this.config.str(0); + const timeout = this.config.uint(1); + + await this.wallet.unlock(passphrase, timeout); + + this.log('Unlocked.'); + } + + async rpc() { + const method = this.argv.shift(); + const params = []; + + for (const arg of this.argv) { + let param; + try { + param = JSON.parse(arg); + } catch (e) { + param = arg; + } + params.push(param); + } + + let result; + try { + result = await this.client.rpc.execute(method, params); + } catch (e) { + if (e.type === 'RPCError') { + this.log(e.message); + return; + } + throw e; + } + + this.log(result); + } + + async handleWallet() { + const network = this.config.str('network', 'main'); + + this.wallet = new WalletClient({ + url: this.config.str('url'), + apiKey: this.config.str('api-key'), + ssl: this.config.bool('ssl'), + host: this.config.str('http-host'), + port: this.config.uint('http-port') + || wports[network] + || wports.main, + id: this.config.str('id', 'primary'), + token: this.config.str('token') + }); + + switch (this.argv.shift()) { + case 'listen': + await this.listenWallet(); + break; + case 'get': + await this.getWallet(); + break; + case 'master': + await this.getMaster(); + break; + case 'shared': + if (this.argv[0] === 'add') { + this.argv.shift(); + await this.addSharedKey(); + break; + } + if (this.argv[0] === 'remove') { + this.argv.shift(); + await this.removeSharedKey(); + break; + } + if (this.argv[0] === 'list') + this.argv.shift(); + await this.getSharedKeys(); + break; + case 'balance': + await this.getBalance(); + break; + case 'history': + await this.getWalletHistory(); + break; + case 'pending': + await this.getWalletPending(); + break; + case 'coins': + await this.getWalletCoins(); + break; + case 'account': + if (this.argv[0] === 'list') { + this.argv.shift(); + await this.getAccounts(); + break; + } + if (this.argv[0] === 'create') { + this.argv.shift(); + await this.createAccount(); + break; + } + if (this.argv[0] === 'get') + this.argv.shift(); + await this.getAccount(); + break; + case 'address': + await this.createAddress(); + break; + case 'change': + await this.createChange(); + break; + case 'nested': + await this.createNested(); + break; + case 'retoken': + await this.retoken(); + break; + case 'sign': + await this.signTX(); + break; + case 'mktx': + await this.createTX(); + break; + case 'send': + await this.sendTX(); + break; + case 'zap': + await this.zapWallet(); + break; + case 'tx': + await this.getDetails(); + break; + case 'blocks': + await this.getWalletBlocks(); + break; + case 'block': + await this.getWalletBlock(); + break; + case 'view': + await this.viewTX(); + break; + case 'import': + await this.importKey(); + break; + case 'watch': + await this.importAddress(); + break; + case 'key': + await this.getKey(); + break; + case 'dump': + await this.getWIF(); + break; + case 'lock': + await this.lock(); + break; + case 'unlock': + await this.unlock(); + break; + case 'resend': + await this.resendWallet(); + break; + case 'rescan': + await this.rescan(); + break; + default: + this.log('Unrecognized command.'); + this.log('Commands:'); + this.log(' $ listen: Listen for events.'); + this.log(' $ get: View wallet.'); + this.log(' $ master: View wallet master key.'); + this.log(' $ shared add [xpubkey]: Add key to wallet.'); + this.log(' $ shared remove [xpubkey]: Remove key from wallet.'); + this.log(' $ balance: Get wallet balance.'); + this.log(' $ history: View TX history.'); + this.log(' $ pending: View pending TXs.'); + this.log(' $ coins: View wallet coins.'); + this.log(' $ account list: List account names.'); + this.log(' $ account create [account-name]: Create account.'); + this.log(' $ account get [account-name]: Get account details.'); + this.log(' $ address: Derive new address.'); + this.log(' $ change: Derive new change address.'); + this.log(' $ nested: Derive new nested address.'); + this.log(' $ retoken: Create new api key.'); + this.log(' $ send [address] [value]: Send transaction.'); + this.log(' $ mktx [address] [value]: Create transaction.'); + this.log(' $ sign [tx-hex]: Sign transaction.'); + this.log(' $ zap [age?]: Zap pending wallet TXs.'); + this.log(' $ tx [hash]: View transaction details.'); + this.log(' $ blocks: List wallet blocks.'); + this.log(' $ block [height]: View wallet block.'); + this.log(' $ view [tx-hex]: Parse and view transaction.'); + this.log(' $ import [wif|hex]: Import private or public key.'); + this.log(' $ watch [address]: Import an address.'); + this.log(' $ key [address]: Get wallet key by address.'); + this.log(' $ dump [address]: Get wallet key WIF by address.'); + this.log(' $ lock: Lock wallet.'); + this.log(' $ unlock [passphrase] [timeout?]: Unlock wallet.'); + this.log(' $ resend: Resend pending transactions.'); + this.log(' $ rescan [height]: Rescan for transactions.'); + this.log('Other Options:'); + this.log(' --passphrase [passphrase]: For signing & account creation.'); + this.log(' --account [account-name]: Account name.'); + break; + } + } + + async handleNode() { + const network = this.config.str('network', 'main'); + + this.client = new NodeClient({ + url: this.config.str('url'), + apiKey: this.config.str('api-key'), + ssl: this.config.bool('ssl'), + host: this.config.str('http-host'), + port: this.config.uint('http-port') + || nports[network] + || nports.main + }); + + switch (this.argv.shift()) { + case 'info': + await this.getInfo(); + break; + case 'wallets': + await this.getWallets(); + break; + case 'mkwallet': + await this.createWallet(); + break; + case 'broadcast': + await this.broadcast(); + break; + case 'mempool': + await this.getMempool(); + break; + case 'tx': + await this.getTX(); + break; + case 'coin': + await this.getCoin(); + break; + case 'block': + await this.getBlock(); + break; + case 'reset': + await this.reset(); + break; + case 'resend': + await this.resend(); + break; + case 'backup': + await this.backup(); + break; + case 'rpc': + await this.rpc(); + break; + default: + this.log('Unrecognized command.'); + this.log('Commands:'); + this.log(' $ info: Get server info.'); + this.log(' $ wallets: List all wallets.'); + this.log(' $ wallet create [id]: Create wallet.'); + this.log(' $ broadcast [tx-hex]: Broadcast transaction.'); + this.log(' $ mempool: Get mempool snapshot.'); + this.log(' $ tx [hash/address]: View transactions.'); + this.log(' $ coin [hash+index/address]: View coins.'); + this.log(' $ block [hash/height]: View block.'); + this.log(' $ reset [height/hash]: Reset chain to desired block.'); + this.log(' $ resend: Resend pending transactions.'); + this.log(' $ backup [path]: Backup the wallet db.'); + this.log(' $ rpc [command] [args]: Execute RPC command.'); + break; + } + } + + async open() { + switch (this.argv[0]) { + case 'w': + case 'wallet': + this.argv.shift(); + if (this.argv[0] === 'create') { + this.argv[0] = 'mkwallet'; + await this.handleNode(); + break; + } + await this.handleWallet(); + break; + default: await this.handleNode(); break; - } - await this.handleWallet(); - break; - default: - await this.handleNode(); - break; + } } -}; -CLI.prototype.destroy = async function destroy() { - if (this.wallet && this.wallet.opened) - await this.wallet.close(); + async destroy() { + if (this.wallet && this.wallet.opened) + await this.wallet.close(); - if (this.client && this.client.opened) - await this.client.close(); -}; + if (this.client && this.client.opened) + await this.client.close(); + } +} (async () => { const cli = new CLI(); diff --git a/browser/proxysocket.js b/browser/proxysocket.js index 544e1fb3..8267f3f8 100644 --- a/browser/proxysocket.js +++ b/browser/proxysocket.js @@ -12,157 +12,163 @@ const bsock = require('bsock'); const hash256 = require('bcrypto/lib/hash256'); const bio = require('bufio'); -function ProxySocket(uri) { - if (!(this instanceof ProxySocket)) - return new ProxySocket(uri); +class ProxySocket extends EventEmitter { + constructor(uri) { + super(); - EventEmitter.call(this); + this.info = null; - this.info = null; + this.socket = bsock.connect(uri); + this.sendBuffer = []; + this.recvBuffer = []; + this.paused = false; + this.snonce = null; + this.bytesWritten = 0; + this.bytesRead = 0; + this.remoteAddress = null; + this.remotePort = 0; - this.socket = bsock.connect(uri); - this.sendBuffer = []; - this.recvBuffer = []; - this.paused = false; - this.snonce = null; - this.bytesWritten = 0; - this.bytesRead = 0; - this.remoteAddress = null; - this.remotePort = 0; + this.closed = false; - this.closed = false; + this.init(); + } - this.init(); -} + init() { + this.socket.bind('info', (info) => { + if (this.closed) + return; -Object.setPrototypeOf(ProxySocket.prototype, EventEmitter.prototype); + this.info = info; -ProxySocket.prototype.init = function init() { - this.socket.bind('info', (info) => { - if (this.closed) - return; + if (info.pow) { + this.snonce = Buffer.from(info.snonce, 'hex'); + this.target = Buffer.from(info.target, 'hex'); + } - this.info = info; + this.emit('info', info); + }); - if (info.pow) { - this.snonce = Buffer.from(info.snonce, 'hex'); - this.target = Buffer.from(info.target, 'hex'); - } + this.socket.on('error', (err) => { + console.error(err); + }); - this.emit('info', info); - }); + this.socket.bind('tcp connect', (addr, port) => { + if (this.closed) + return; + this.remoteAddress = addr; + this.remotePort = port; + this.emit('connect'); + }); - this.socket.on('error', (err) => { - console.error(err); - }); + this.socket.bind('tcp data', (data) => { + data = Buffer.from(data, 'hex'); + if (this.paused) { + this.recvBuffer.push(data); + return; + } + this.bytesRead += data.length; + this.emit('data', data); + }); - this.socket.bind('tcp connect', (addr, port) => { - if (this.closed) - return; - this.remoteAddress = addr; + this.socket.bind('tcp close', (data) => { + if (this.closed) + return; + this.closed = true; + this.emit('close'); + }); + + this.socket.bind('tcp error', (e) => { + const err = new Error(e.message); + err.code = e.code; + this.emit('error', err); + }); + + this.socket.bind('tcp timeout', () => { + this.emit('timeout'); + }); + + this.socket.bind('disconnect', () => { + if (this.closed) + return; + this.closed = true; + this.emit('close'); + }); + } + + connect(port, host) { + this.remoteAddress = host; this.remotePort = port; - this.emit('connect'); - }); - this.socket.bind('tcp data', (data) => { - data = Buffer.from(data, 'hex'); - if (this.paused) { - this.recvBuffer.push(data); + if (this.closed) { + this.sendBuffer.length = 0; return; } - this.bytesRead += data.length; - this.emit('data', data); - }); - this.socket.bind('tcp close', (data) => { - if (this.closed) + if (!this.info) { + this.once('info', connect.bind(this, port, host)); return; - this.closed = true; - this.emit('close'); - }); + } - this.socket.bind('tcp error', (e) => { - const err = new Error(e.message); - err.code = e.code; - this.emit('error', err); - }); + let nonce = 0; - this.socket.bind('tcp timeout', () => { - this.emit('timeout'); - }); + if (this.info.pow) { + const bw = bio.write(); - this.socket.bind('disconnect', () => { - if (this.closed) - return; - this.closed = true; - this.emit('close'); - }); -}; + bw.writeU32(nonce); + bw.writeBytes(this.snonce); + bw.writeU32(port); + bw.writeString(host, 'ascii'); -ProxySocket.prototype.connect = function connect(port, host) { - this.remoteAddress = host; - this.remotePort = port; + const pow = bw.render(); + + console.log( + 'Solving proof of work to create socket (%d, %s) -- please wait.', + port, host); + + do { + nonce += 1; + assert(nonce <= 0xffffffff, 'Could not create socket.'); + pow.writeUInt32LE(nonce, 0, true); + } while (hash256.digest(pow).compare(this.target) > 0); + + console.log('Solved proof of work: %d', nonce); + } + + this.socket.fire('tcp connect', port, host, nonce); + + for (const chunk of this.sendBuffer) + this.write(chunk); - if (this.closed) { this.sendBuffer.length = 0; - return; } - if (!this.info) { - this.once('info', connect.bind(this, port, host)); - return; + setKeepAlive(enable, delay) { + this.socket.fire('tcp keep alive', enable, delay); } - let nonce = 0; - - if (this.info.pow) { - const bw = bio.write(); - - bw.writeU32(nonce); - bw.writeBytes(this.snonce); - bw.writeU32(port); - bw.writeString(host, 'ascii'); - - const pow = bw.render(); - - console.log( - 'Solving proof of work to create socket (%d, %s) -- please wait.', - port, host); - - do { - nonce += 1; - assert(nonce <= 0xffffffff, 'Could not create socket.'); - pow.writeUInt32LE(nonce, 0, true); - } while (hash256.digest(pow).compare(this.target) > 0); - - console.log('Solved proof of work: %d', nonce); + setNoDelay(enable) { + this.socket.fire('tcp no delay', enable); } - this.socket.fire('tcp connect', port, host, nonce); + setTimeout(timeout, callback) { + this.socket.fire('tcp set timeout', timeout); + if (callback) + this.on('timeout', callback); + } - for (const chunk of this.sendBuffer) - this.write(chunk); + write(data, callback) { + if (!this.info) { + this.sendBuffer.push(data); - this.sendBuffer.length = 0; -}; + if (callback) + callback(); -ProxySocket.prototype.setKeepAlive = function setKeepAlive(enable, delay) { - this.socket.fire('tcp keep alive', enable, delay); -}; + return true; + } -ProxySocket.prototype.setNoDelay = function setNoDelay(enable) { - this.socket.fire('tcp no delay', enable); -}; + this.bytesWritten += data.length; -ProxySocket.prototype.setTimeout = function setTimeout(timeout, callback) { - this.socket.fire('tcp set timeout', timeout); - if (callback) - this.on('timeout', callback); -}; - -ProxySocket.prototype.write = function write(data, callback) { - if (!this.info) { - this.sendBuffer.push(data); + this.socket.fire('tcp data', data.toString('hex')); if (callback) callback(); @@ -170,43 +176,34 @@ ProxySocket.prototype.write = function write(data, callback) { return true; } - this.bytesWritten += data.length; - - this.socket.fire('tcp data', data.toString('hex')); - - if (callback) - callback(); - - return true; -}; - -ProxySocket.prototype.pause = function pause() { - this.paused = true; -}; - -ProxySocket.prototype.resume = function resume() { - const recv = this.recvBuffer; - - this.paused = false; - this.recvBuffer = []; - - for (const data of recv) { - this.bytesRead += data.length; - this.emit('data', data); + pause() { + this.paused = true; } -}; -ProxySocket.prototype.destroy = function destroy() { - if (this.closed) - return; - this.closed = true; - this.socket.destroy(); -}; + resume() { + const recv = this.recvBuffer; -ProxySocket.connect = function connect(uri, port, host) { - const socket = new ProxySocket(uri); - socket.connect(port, host); - return socket; -}; + this.paused = false; + this.recvBuffer = []; + + for (const data of recv) { + this.bytesRead += data.length; + this.emit('data', data); + } + } + + destroy() { + if (this.closed) + return; + this.closed = true; + this.socket.destroy(); + } + + static connect(uri, port, host) { + const socket = new this(uri); + socket.connect(port, host); + return socket; + } +} module.exports = ProxySocket; diff --git a/browser/wsproxy.js b/browser/wsproxy.js index 44d525ba..d6b5748b 100644 --- a/browser/wsproxy.js +++ b/browser/wsproxy.js @@ -12,240 +12,239 @@ const TARGET = Buffer.from( '0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex'); -function WSProxy(options) { - if (!(this instanceof WSProxy)) - return new WSProxy(options); +class WSProxy extends EventEmitter { + constructor(options) { + super(); - EventEmitter.call(this); + if (!options) + options = {}; - if (!options) - options = {}; + this.options = options; + this.target = options.target || TARGET; + this.pow = options.pow === true; + this.ports = new Set(); + this.io = bsock.server(); + this.sockets = new WeakMap(); - this.options = options; - this.target = options.target || TARGET; - this.pow = options.pow === true; - this.ports = new Set(); - this.io = bsock.server(); - this.sockets = new WeakMap(); + if (options.ports) { + for (const port of options.ports) + this.ports.add(port); + } - if (options.ports) { - for (const port of options.ports) - this.ports.add(port); + this.init(); } - this.init(); -} + init() { + this.io.on('error', (err) => { + this.emit('error', err); + }); -Object.setPrototypeOf(WSProxy.prototype, EventEmitter.prototype); - -WSProxy.prototype.init = function init() { - this.io.on('error', (err) => { - this.emit('error', err); - }); - - this.io.on('socket', (ws) => { - this.handleSocket(ws); - }); -}; - -WSProxy.prototype.handleSocket = function handleSocket(ws) { - const state = new SocketState(this, ws); - - // Use a weak map to avoid - // mutating the websocket object. - this.sockets.set(ws, state); - - ws.fire('info', state.toInfo()); - - ws.on('error', (err) => { - this.emit('error', err); - }); - - ws.bind('tcp connect', (port, host, nonce) => { - this.handleConnect(ws, port, host, nonce); - }); -}; - -WSProxy.prototype.handleConnect = function handleConnect(ws, port, host, nonce) { - const state = this.sockets.get(ws); - assert(state); - - if (state.socket) { - this.log('Client is trying to reconnect (%s).', state.host); - return; + this.io.on('socket', (ws) => { + this.handleSocket(ws); + }); } - if ((port & 0xffff) !== port - || typeof host !== 'string' - || host.length === 0) { - this.log('Client gave bad arguments (%s).', state.host); - ws.fire('tcp close'); - ws.destroy(); - return; + handleSocket(ws) { + const state = new SocketState(this, ws); + + // Use a weak map to avoid + // mutating the websocket object. + this.sockets.set(ws, state); + + ws.fire('info', state.toInfo()); + + ws.on('error', (err) => { + this.emit('error', err); + }); + + ws.bind('tcp connect', (port, host, nonce) => { + this.handleConnect(ws, port, host, nonce); + }); } - if (this.pow) { - if ((nonce >>> 0) !== nonce) { - this.log('Client did not solve proof of work (%s).', state.host); + handleConnect(ws, port, host, nonce) { + const state = this.sockets.get(ws); + assert(state); + + if (state.socket) { + this.log('Client is trying to reconnect (%s).', state.host); + return; + } + + if ((port & 0xffff) !== port + || typeof host !== 'string' + || host.length === 0) { + this.log('Client gave bad arguments (%s).', state.host); ws.fire('tcp close'); ws.destroy(); return; } - const bw = bio.write(); - bw.writeU32(nonce); - bw.writeBytes(state.snonce); - bw.writeU32(port); - bw.writeString(host, 'ascii'); + if (this.pow) { + if ((nonce >>> 0) !== nonce) { + this.log('Client did not solve proof of work (%s).', state.host); + ws.fire('tcp close'); + ws.destroy(); + return; + } - const pow = bw.render(); + const bw = bio.write(); + bw.writeU32(nonce); + bw.writeBytes(state.snonce); + bw.writeU32(port); + bw.writeString(host, 'ascii'); - if (hash256.digest(pow).compare(this.target) > 0) { - this.log('Client did not solve proof of work (%s).', state.host); - ws.fire('tcp close'); + const pow = bw.render(); + + if (hash256.digest(pow).compare(this.target) > 0) { + this.log('Client did not solve proof of work (%s).', state.host); + ws.fire('tcp close'); + ws.destroy(); + return; + } + } + + let raw, addr; + try { + raw = IP.toBuffer(host); + addr = IP.toString(raw); + } catch (e) { + this.log('Client gave a bad host: %s (%s).', host, state.host); + ws.fire('tcp error', { + message: 'EHOSTUNREACH', + code: 'EHOSTUNREACH' + }); ws.destroy(); return; } - } - let raw, addr; - try { - raw = IP.toBuffer(host); - addr = IP.toString(raw); - } catch (e) { - this.log('Client gave a bad host: %s (%s).', host, state.host); - ws.fire('tcp error', { - message: 'EHOSTUNREACH', - code: 'EHOSTUNREACH' - }); - ws.destroy(); - return; - } - - if (!IP.isRoutable(raw) || IP.isOnion(raw)) { - this.log( - 'Client is trying to connect to a bad ip: %s (%s).', - addr, state.host); - ws.fire('tcp error', { - message: 'ENETUNREACH', - code: 'ENETUNREACH' - }); - ws.destroy(); - return; - } - - if (!this.ports.has(port)) { - this.log('Client is connecting to non-whitelist port (%s).', state.host); - ws.fire('tcp error', { - message: 'ENETUNREACH', - code: 'ENETUNREACH' - }); - ws.destroy(); - return; - } - - let socket; - try { - socket = state.connect(port, addr); - this.log('Connecting to %s (%s).', state.remoteHost, state.host); - } catch (e) { - this.log(e.message); - this.log('Closing %s (%s).', state.remoteHost, state.host); - ws.fire('tcp error', { - message: 'ENETUNREACH', - code: 'ENETUNREACH' - }); - ws.destroy(); - return; - } - - socket.on('connect', () => { - ws.fire('tcp connect', socket.remoteAddress, socket.remotePort); - }); - - socket.on('data', (data) => { - ws.fire('tcp data', data.toString('hex')); - }); - - socket.on('error', (err) => { - ws.fire('tcp error', { - message: err.message, - code: err.code || null - }); - }); - - socket.on('timeout', () => { - ws.fire('tcp timeout'); - }); - - socket.on('close', () => { - this.log('Closing %s (%s).', state.remoteHost, state.host); - ws.fire('tcp close'); - ws.destroy(); - }); - - ws.bind('tcp data', (data) => { - if (typeof data !== 'string') + if (!IP.isRoutable(raw) || IP.isOnion(raw)) { + this.log( + 'Client is trying to connect to a bad ip: %s (%s).', + addr, state.host); + ws.fire('tcp error', { + message: 'ENETUNREACH', + code: 'ENETUNREACH' + }); + ws.destroy(); return; - socket.write(Buffer.from(data, 'hex')); - }); + } - ws.bind('tcp keep alive', (enable, delay) => { - socket.setKeepAlive(enable, delay); - }); + if (!this.ports.has(port)) { + this.log('Client is connecting to non-whitelist port (%s).', state.host); + ws.fire('tcp error', { + message: 'ENETUNREACH', + code: 'ENETUNREACH' + }); + ws.destroy(); + return; + } - ws.bind('tcp no delay', (enable) => { - socket.setNoDelay(enable); - }); + let socket; + try { + socket = state.connect(port, addr); + this.log('Connecting to %s (%s).', state.remoteHost, state.host); + } catch (e) { + this.log(e.message); + this.log('Closing %s (%s).', state.remoteHost, state.host); + ws.fire('tcp error', { + message: 'ENETUNREACH', + code: 'ENETUNREACH' + }); + ws.destroy(); + return; + } - ws.bind('tcp set timeout', (timeout) => { - socket.setTimeout(timeout); - }); + socket.on('connect', () => { + ws.fire('tcp connect', socket.remoteAddress, socket.remotePort); + }); - ws.bind('tcp pause', () => { - socket.pause(); - }); + socket.on('data', (data) => { + ws.fire('tcp data', data.toString('hex')); + }); - ws.bind('tcp resume', () => { - socket.resume(); - }); + socket.on('error', (err) => { + ws.fire('tcp error', { + message: err.message, + code: err.code || null + }); + }); - ws.bind('disconnect', () => { - socket.destroy(); - }); -}; + socket.on('timeout', () => { + ws.fire('tcp timeout'); + }); -WSProxy.prototype.log = function log(...args) { - process.stdout.write('wsproxy: '); - console.log(...args); -}; + socket.on('close', () => { + this.log('Closing %s (%s).', state.remoteHost, state.host); + ws.fire('tcp close'); + ws.destroy(); + }); -WSProxy.prototype.attach = function attach(server) { - this.io.attach(server); -}; + ws.bind('tcp data', (data) => { + if (typeof data !== 'string') + return; + socket.write(Buffer.from(data, 'hex')); + }); -function SocketState(server, socket) { - this.pow = server.pow; - this.target = server.target; - this.snonce = nonce(); - this.socket = null; - this.host = socket.host; - this.remoteHost = null; + ws.bind('tcp keep alive', (enable, delay) => { + socket.setKeepAlive(enable, delay); + }); + + ws.bind('tcp no delay', (enable) => { + socket.setNoDelay(enable); + }); + + ws.bind('tcp set timeout', (timeout) => { + socket.setTimeout(timeout); + }); + + ws.bind('tcp pause', () => { + socket.pause(); + }); + + ws.bind('tcp resume', () => { + socket.resume(); + }); + + ws.bind('disconnect', () => { + socket.destroy(); + }); + } + + log(...args) { + process.stdout.write('wsproxy: '); + console.log(...args); + } + + attach(server) { + this.io.attach(server); + } } -SocketState.prototype.toInfo = function toInfo() { - return { - pow: this.pow, - target: this.target.toString('hex'), - snonce: this.snonce.toString('hex') - }; -}; +class SocketState { + constructor(server, socket) { + this.pow = server.pow; + this.target = server.target; + this.snonce = nonce(); + this.socket = null; + this.host = socket.host; + this.remoteHost = null; + } -SocketState.prototype.connect = function connect(port, host) { - this.socket = net.connect(port, host); - this.remoteHost = IP.toHostname(host, port); - return this.socket; -}; + toInfo() { + return { + pow: this.pow, + target: this.target.toString('hex'), + snonce: this.snonce.toString('hex') + }; + } + + connect(port, host) { + this.socket = net.connect(port, host); + this.remoteHost = IP.toHostname(host, port); + return this.socket; + } +} function nonce() { const buf = Buffer.allocUnsafe(8); diff --git a/test/util/memwallet.js b/test/util/memwallet.js index 8f7365c1..7e9c2725 100644 --- a/test/util/memwallet.js +++ b/test/util/memwallet.js @@ -15,412 +15,418 @@ const KeyRing = require('../../lib/primitives/keyring'); const Outpoint = require('../../lib/primitives/outpoint'); const Coin = require('../../lib/primitives/coin'); -function MemWallet(options) { - if (!(this instanceof MemWallet)) - return new MemWallet(options); +class MemWallet { + constructor(options) { + this.network = Network.primary; + this.master = null; + this.key = null; + this.witness = false; + this.account = 0; + this.receiveDepth = 1; + this.changeDepth = 1; + this.receive = null; + this.change = null; + this.map = new Set(); + this.coins = new Map(); + this.spent = new Map(); + this.paths = new Map(); + this.balance = 0; + this.txs = 0; + this.filter = BloomFilter.fromRate(1000000, 0.001, -1); - this.network = Network.primary; - this.master = null; - this.key = null; - this.witness = false; - this.account = 0; - this.receiveDepth = 1; - this.changeDepth = 1; - this.receive = null; - this.change = null; - this.map = new Set(); - this.coins = new Map(); - this.spent = new Map(); - this.paths = new Map(); - this.balance = 0; - this.txs = 0; - this.filter = BloomFilter.fromRate(1000000, 0.001, -1); + if (options) + this.fromOptions(options); - if (options) - this.fromOptions(options); + this.init(); + } - this.init(); + fromOptions(options) { + if (options.network != null) { + assert(options.network); + this.network = Network.get(options.network); + } + + if (options.master != null) { + assert(options.master); + this.master = HD.PrivateKey.fromOptions(options.master, this.network); + } + + if (options.key != null) { + assert(HD.isPrivate(options.key)); + this.key = options.key; + } + + if (options.witness != null) { + assert(typeof options.witness === 'boolean'); + this.witness = options.witness; + } + + if (options.account != null) { + assert(typeof options.account === 'number'); + this.account = options.account; + } + + if (options.receiveDepth != null) { + assert(typeof options.receiveDepth === 'number'); + this.receiveDepth = options.receiveDepth; + } + + if (options.changeDepth != null) { + assert(typeof options.changeDepth === 'number'); + this.changeDepth = options.changeDepth; + } + + return this; + } + + init() { + let i; + + if (!this.master) + this.master = HD.PrivateKey.generate(); + + if (!this.key) { + const type = this.network.keyPrefix.coinType; + this.key = this.master.deriveAccount(44, type, this.account); + } + + i = this.receiveDepth; + while (i--) + this.createReceive(); + + i = this.changeDepth; + while (i--) + this.createChange(); + } + + createReceive() { + const index = this.receiveDepth++; + const key = this.deriveReceive(index); + const hash = key.getHash('hex'); + this.filter.add(hash, 'hex'); + this.paths.set(hash, new Path(hash, 0, index)); + this.receive = key; + return key; + } + + createChange() { + const index = this.changeDepth++; + const key = this.deriveChange(index); + const hash = key.getHash('hex'); + this.filter.add(hash, 'hex'); + this.paths.set(hash, new Path(hash, 1, index)); + this.change = key; + return key; + } + + deriveReceive(index) { + return this.deriveKey(0, index); + } + + deriveChange(index) { + return this.deriveKey(1, index); + } + + derivePath(path) { + return this.deriveKey(path.branch, path.index); + } + + deriveKey(branch, index) { + const type = this.network.keyPrefix.coinType; + + let key = this.master.deriveAccount(44, type, this.account); + + key = key.derive(branch).derive(index); + + const ring = new KeyRing({ + network: this.network, + privateKey: key.privateKey, + witness: this.witness + }); + + ring.witness = this.witness; + + return ring; + } + + getKey(hash) { + const path = this.paths.get(hash); + + if (!path) + return null; + + return this.derivePath(path); + } + + getPath(hash) { + return this.paths.get(hash); + } + + getCoin(key) { + return this.coins.get(key); + } + + getUndo(key) { + return this.spent.get(key); + } + + addCoin(coin) { + const op = new Outpoint(coin.hash, coin.index); + const key = op.toKey(); + + this.filter.add(op.toRaw()); + + this.spent.delete(key); + + this.coins.set(key, coin); + this.balance += coin.value; + } + + removeCoin(key) { + const coin = this.coins.get(key); + + if (!coin) + return; + + this.spent.set(key, coin); + this.balance -= coin.value; + + this.coins.delete(key); + } + + getAddress() { + return this.receive.getAddress(); + } + + getReceive() { + return this.receive.getAddress(); + } + + getChange() { + return this.change.getAddress(); + } + + getCoins() { + const coins = []; + + for (const coin of this.coins.values()) + coins.push(coin); + + return coins; + } + + syncKey(path) { + switch (path.branch) { + case 0: + if (path.index === this.receiveDepth - 1) + this.createReceive(); + break; + case 1: + if (path.index === this.changeDepth - 1) + this.createChange(); + break; + default: + assert(false); + break; + } + } + + addBlock(entry, txs) { + for (let i = 0; i < txs.length; i++) { + const tx = txs[i]; + this.addTX(tx, entry.height); + } + } + + removeBlock(entry, txs) { + for (let i = txs.length - 1; i >= 0; i--) { + const tx = txs[i]; + this.removeTX(tx, entry.height); + } + } + + addTX(tx, height) { + const hash = tx.hash('hex'); + let result = false; + + if (height == null) + height = -1; + + if (this.map.has(hash)) + return true; + + for (let i = 0; i < tx.inputs.length; i++) { + const input = tx.inputs[i]; + const op = input.prevout.toKey(); + const coin = this.getCoin(op); + + if (!coin) + continue; + + result = true; + + this.removeCoin(op); + } + + for (let i = 0; i < tx.outputs.length; i++) { + const output = tx.outputs[i]; + const addr = output.getHash('hex'); + + if (!addr) + continue; + + const path = this.getPath(addr); + + if (!path) + continue; + + result = true; + + const coin = Coin.fromTX(tx, i, height); + + this.addCoin(coin); + this.syncKey(path); + } + + if (result) { + this.txs += 1; + this.map.add(hash); + } + + return result; + } + + removeTX(tx, height) { + const hash = tx.hash('hex'); + let result = false; + + if (!this.map.has(hash)) + return false; + + for (let i = 0; i < tx.outputs.length; i++) { + const op = new Outpoint(hash, i).toKey(); + const coin = this.getCoin(op); + + if (!coin) + continue; + + result = true; + + this.removeCoin(op); + } + + for (let i = 0; i < tx.inputs.length; i++) { + const input = tx.inputs[i]; + const op = input.prevout.toKey(); + const coin = this.getUndo(op); + + if (!coin) + continue; + + result = true; + + this.addCoin(coin); + } + + if (result) + this.txs -= 1; + + this.map.delete(hash); + + return result; + } + + deriveInputs(mtx) { + const keys = []; + + for (let i = 0; i < mtx.inputs.length; i++) { + const input = mtx.inputs[i]; + const coin = mtx.view.getOutputFor(input); + + if (!coin) + continue; + + const addr = coin.getHash('hex'); + + if (!addr) + continue; + + const path = this.getPath(addr); + + if (!path) + continue; + + const key = this.derivePath(path); + + keys.push(key); + } + + return keys; + } + + fund(mtx, options) { + const coins = this.getCoins(); + + if (!options) + options = {}; + + return mtx.fund(coins, { + selection: options.selection || 'age', + round: options.round, + depth: options.depth, + hardFee: options.hardFee, + subtractFee: options.subtractFee, + changeAddress: this.getChange(), + height: -1, + rate: options.rate, + maxFee: options.maxFee + }); + } + + template(mtx) { + const keys = this.deriveInputs(mtx); + mtx.template(keys); + } + + sign(mtx) { + const keys = this.deriveInputs(mtx); + mtx.template(keys); + mtx.sign(keys); + } + + async create(options) { + const mtx = new MTX(options); + + await this.fund(mtx, options); + + assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); + + mtx.sortMembers(); + + if (options.locktime != null) + mtx.setLocktime(options.locktime); + + this.sign(mtx); + + if (!mtx.isSigned()) + throw new Error('Cannot sign tx.'); + + return mtx; + } + + async send(options) { + const mtx = await this.create(options); + this.addTX(mtx.toTX()); + return mtx; + } } -MemWallet.prototype.fromOptions = function fromOptions(options) { - if (options.network != null) { - assert(options.network); - this.network = Network.get(options.network); +class Path { + constructor(hash, branch, index) { + this.hash = hash; + this.branch = branch; + this.index = index; } - - if (options.master != null) { - assert(options.master); - this.master = HD.PrivateKey.fromOptions(options.master, this.network); - } - - if (options.key != null) { - assert(HD.isPrivate(options.key)); - this.key = options.key; - } - - if (options.witness != null) { - assert(typeof options.witness === 'boolean'); - this.witness = options.witness; - } - - if (options.account != null) { - assert(typeof options.account === 'number'); - this.account = options.account; - } - - if (options.receiveDepth != null) { - assert(typeof options.receiveDepth === 'number'); - this.receiveDepth = options.receiveDepth; - } - - if (options.changeDepth != null) { - assert(typeof options.changeDepth === 'number'); - this.changeDepth = options.changeDepth; - } - - return this; -}; - -MemWallet.prototype.init = function init() { - let i; - - if (!this.master) - this.master = HD.PrivateKey.generate(); - - if (!this.key) { - const type = this.network.keyPrefix.coinType; - this.key = this.master.deriveAccount(44, type, this.account); - } - - i = this.receiveDepth; - while (i--) - this.createReceive(); - - i = this.changeDepth; - while (i--) - this.createChange(); -}; - -MemWallet.prototype.createReceive = function createReceive() { - const index = this.receiveDepth++; - const key = this.deriveReceive(index); - const hash = key.getHash('hex'); - this.filter.add(hash, 'hex'); - this.paths.set(hash, new Path(hash, 0, index)); - this.receive = key; - return key; -}; - -MemWallet.prototype.createChange = function createChange() { - const index = this.changeDepth++; - const key = this.deriveChange(index); - const hash = key.getHash('hex'); - this.filter.add(hash, 'hex'); - this.paths.set(hash, new Path(hash, 1, index)); - this.change = key; - return key; -}; - -MemWallet.prototype.deriveReceive = function deriveReceive(index) { - return this.deriveKey(0, index); -}; - -MemWallet.prototype.deriveChange = function deriveChange(index) { - return this.deriveKey(1, index); -}; - -MemWallet.prototype.derivePath = function derivePath(path) { - return this.deriveKey(path.branch, path.index); -}; - -MemWallet.prototype.deriveKey = function deriveKey(branch, index) { - const type = this.network.keyPrefix.coinType; - let key = this.master.deriveAccount(44, type, this.account); - key = key.derive(branch).derive(index); - const ring = new KeyRing({ - network: this.network, - privateKey: key.privateKey, - witness: this.witness - }); - ring.witness = this.witness; - return ring; -}; - -MemWallet.prototype.getKey = function getKey(hash) { - const path = this.paths.get(hash); - - if (!path) - return null; - - return this.derivePath(path); -}; - -MemWallet.prototype.getPath = function getPath(hash) { - return this.paths.get(hash); -}; - -MemWallet.prototype.getCoin = function getCoin(key) { - return this.coins.get(key); -}; - -MemWallet.prototype.getUndo = function getUndo(key) { - return this.spent.get(key); -}; - -MemWallet.prototype.addCoin = function addCoin(coin) { - const op = new Outpoint(coin.hash, coin.index); - const key = op.toKey(); - - this.filter.add(op.toRaw()); - - this.spent.delete(key); - - this.coins.set(key, coin); - this.balance += coin.value; -}; - -MemWallet.prototype.removeCoin = function removeCoin(key) { - const coin = this.coins.get(key); - - if (!coin) - return; - - this.spent.set(key, coin); - this.balance -= coin.value; - - this.coins.delete(key); -}; - -MemWallet.prototype.getAddress = function getAddress() { - return this.receive.getAddress(); -}; - -MemWallet.prototype.getReceive = function getReceive() { - return this.receive.getAddress(); -}; - -MemWallet.prototype.getChange = function getChange() { - return this.change.getAddress(); -}; - -MemWallet.prototype.getCoins = function getCoins() { - const coins = []; - - for (const coin of this.coins.values()) - coins.push(coin); - - return coins; -}; - -MemWallet.prototype.syncKey = function syncKey(path) { - switch (path.branch) { - case 0: - if (path.index === this.receiveDepth - 1) - this.createReceive(); - break; - case 1: - if (path.index === this.changeDepth - 1) - this.createChange(); - break; - default: - assert(false); - break; - } -}; - -MemWallet.prototype.addBlock = function addBlock(entry, txs) { - for (let i = 0; i < txs.length; i++) { - const tx = txs[i]; - this.addTX(tx, entry.height); - } -}; - -MemWallet.prototype.removeBlock = function removeBlock(entry, txs) { - for (let i = txs.length - 1; i >= 0; i--) { - const tx = txs[i]; - this.removeTX(tx, entry.height); - } -}; - -MemWallet.prototype.addTX = function addTX(tx, height) { - const hash = tx.hash('hex'); - let result = false; - - if (height == null) - height = -1; - - if (this.map.has(hash)) - return true; - - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const op = input.prevout.toKey(); - const coin = this.getCoin(op); - - if (!coin) - continue; - - result = true; - - this.removeCoin(op); - } - - for (let i = 0; i < tx.outputs.length; i++) { - const output = tx.outputs[i]; - const addr = output.getHash('hex'); - - if (!addr) - continue; - - const path = this.getPath(addr); - - if (!path) - continue; - - result = true; - - const coin = Coin.fromTX(tx, i, height); - - this.addCoin(coin); - this.syncKey(path); - } - - if (result) { - this.txs++; - this.map.add(hash); - } - - return result; -}; - -MemWallet.prototype.removeTX = function removeTX(tx, height) { - const hash = tx.hash('hex'); - let result = false; - - if (!this.map.has(hash)) - return false; - - for (let i = 0; i < tx.outputs.length; i++) { - const op = new Outpoint(hash, i).toKey(); - const coin = this.getCoin(op); - - if (!coin) - continue; - - result = true; - - this.removeCoin(op); - } - - for (let i = 0; i < tx.inputs.length; i++) { - const input = tx.inputs[i]; - const op = input.prevout.toKey(); - const coin = this.getUndo(op); - - if (!coin) - continue; - - result = true; - - this.addCoin(coin); - } - - if (result) - this.txs--; - - this.map.delete(hash); - - return result; -}; - -MemWallet.prototype.deriveInputs = function deriveInputs(mtx) { - const keys = []; - - for (let i = 0; i < mtx.inputs.length; i++) { - const input = mtx.inputs[i]; - const coin = mtx.view.getOutputFor(input); - - if (!coin) - continue; - - const addr = coin.getHash('hex'); - - if (!addr) - continue; - - const path = this.getPath(addr); - - if (!path) - continue; - - const key = this.derivePath(path); - - keys.push(key); - } - - return keys; -}; - -MemWallet.prototype.fund = function fund(mtx, options) { - const coins = this.getCoins(); - - if (!options) - options = {}; - - return mtx.fund(coins, { - selection: options.selection || 'age', - round: options.round, - depth: options.depth, - hardFee: options.hardFee, - subtractFee: options.subtractFee, - changeAddress: this.getChange(), - height: -1, - rate: options.rate, - maxFee: options.maxFee - }); -}; - -MemWallet.prototype.template = function template(mtx) { - const keys = this.deriveInputs(mtx); - mtx.template(keys); -}; - -MemWallet.prototype.sign = function sign(mtx) { - const keys = this.deriveInputs(mtx); - mtx.template(keys); - mtx.sign(keys); -}; - -MemWallet.prototype.create = async function create(options) { - const mtx = new MTX(options); - - await this.fund(mtx, options); - - assert(mtx.getFee() <= MTX.Selector.MAX_FEE, 'TX exceeds MAX_FEE.'); - - mtx.sortMembers(); - - if (options.locktime != null) - mtx.setLocktime(options.locktime); - - this.sign(mtx); - - if (!mtx.isSigned()) - throw new Error('Cannot sign tx.'); - - return mtx; -}; - -MemWallet.prototype.send = async function send(options) { - const mtx = await this.create(options); - this.addTX(mtx.toTX()); - return mtx; -}; - -function Path(hash, branch, index) { - this.hash = hash; - this.branch = branch; - this.index = index; } module.exports = MemWallet; diff --git a/test/util/node-context.js b/test/util/node-context.js index ddf65bc1..d6d24c7a 100644 --- a/test/util/node-context.js +++ b/test/util/node-context.js @@ -5,119 +5,119 @@ const FullNode = require('../../lib/node/fullnode'); const Network = require('../../lib/protocol/network'); const Logger = require('blgr'); -function NodeContext(network, size) { - if (!(this instanceof NodeContext)) - return new NodeContext(network, size); +class NodeContext { + constructor(network, size) { + this.network = Network.get(network); + this.size = size || 4; + this.nodes = []; - this.network = Network.get(network); - this.size = size || 4; - this.nodes = []; - - this.init(); -}; - -NodeContext.prototype.init = function init() { - for (let i = 0; i < this.size; i++) { - const port = this.network.port + i; - let last = port - 1; - - if (last < this.network.port) - last = port; - - const node = new FullNode({ - network: this.network, - db: 'memory', - logger: new Logger({ - level: 'debug', - file: false, - console: false - }), - listen: true, - publicHost: '127.0.0.1', - publicPort: port, - httpPort: port + 100, - host: '127.0.0.1', - port: port, - seeds: [ - `127.0.0.1:${last}` - ] - }); - - node.on('error', (err) => { - node.logger.error(err); - }); - - this.nodes.push(node); + this.init(); } -}; -NodeContext.prototype.open = function open() { - const jobs = []; + init() { + for (let i = 0; i < this.size; i++) { + const port = this.network.port + i; - for (const node of this.nodes) - jobs.push(node.open()); + let last = port - 1; - return Promise.all(jobs); -}; + if (last < this.network.port) + last = port; -NodeContext.prototype.close = function close() { - const jobs = []; + const node = new FullNode({ + network: this.network, + db: 'memory', + logger: new Logger({ + level: 'debug', + file: false, + console: false + }), + listen: true, + publicHost: '127.0.0.1', + publicPort: port, + httpPort: port + 100, + host: '127.0.0.1', + port: port, + seeds: [ + `127.0.0.1:${last}` + ] + }); - for (const node of this.nodes) - jobs.push(node.close()); + node.on('error', (err) => { + node.logger.error(err); + }); - return Promise.all(jobs); -}; - -NodeContext.prototype.connect = async function connect() { - for (const node of this.nodes) { - await node.connect(); - await new Promise(r => setTimeout(r, 1000)); + this.nodes.push(node); + } } -}; -NodeContext.prototype.disconnect = async function disconnect() { - for (let i = this.nodes.length - 1; i >= 0; i--) { - const node = this.nodes[i]; - await node.disconnect(); - await new Promise(r => setTimeout(r, 1000)); + open() { + const jobs = []; + + for (const node of this.nodes) + jobs.push(node.open()); + + return Promise.all(jobs); } -}; -NodeContext.prototype.startSync = function startSync() { - for (const node of this.nodes) { - node.chain.synced = true; - node.chain.emit('full'); - node.startSync(); + close() { + const jobs = []; + + for (const node of this.nodes) + jobs.push(node.close()); + + return Promise.all(jobs); } -}; -NodeContext.prototype.stopSync = function stopSync() { - for (const node of this.nodes) - node.stopSync(); -}; - -NodeContext.prototype.generate = async function generate(index, blocks) { - const node = this.nodes[index]; - - assert(node); - - for (let i = 0; i < blocks; i++) { - const block = await node.miner.mineBlock(); - await node.chain.add(block); + async connect() { + for (const node of this.nodes) { + await node.connect(); + await new Promise(r => setTimeout(r, 1000)); + } } -}; -NodeContext.prototype.height = function height(index) { - const node = this.nodes[index]; + async disconnect() { + for (let i = this.nodes.length - 1; i >= 0; i--) { + const node = this.nodes[i]; + await node.disconnect(); + await new Promise(r => setTimeout(r, 1000)); + } + } - assert(node); + startSync() { + for (const node of this.nodes) { + node.chain.synced = true; + node.chain.emit('full'); + node.startSync(); + } + } - return node.chain.height; -}; + stopSync() { + for (const node of this.nodes) + node.stopSync(); + } -NodeContext.prototype.sync = async function sync() { - return new Promise(r => setTimeout(r, 3000)); -}; + async generate(index, blocks) { + const node = this.nodes[index]; + + assert(node); + + for (let i = 0; i < blocks; i++) { + const block = await node.miner.mineBlock(); + await node.chain.add(block); + } + } + + height(index) { + const node = this.nodes[index]; + + assert(node); + + return node.chain.height; + } + + async sync() { + return new Promise(r => setTimeout(r, 3000)); + } +} module.exports = NodeContext;